diff --git a/.cloud-init.metadata b/.cloud-init.metadata index f7516a9..9b7ca96 100644 --- a/.cloud-init.metadata +++ b/.cloud-init.metadata @@ -1 +1 @@ -a862d6618a4c56c79d3fb0e279f6c93d0f0141cd SOURCES/cloud-init-18.5.tar.gz +5f4de38850f9691dc9789bd4db4be512c9717d7b SOURCES/cloud-init-19.4.tar.gz diff --git a/.gitignore b/.gitignore index e2ea71d..cc9fcc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/cloud-init-18.5.tar.gz +SOURCES/cloud-init-19.4.tar.gz diff --git a/SOURCES/0001-Add-initial-redhat-setup.patch b/SOURCES/0001-Add-initial-redhat-setup.patch index 6cdf59c..44e4a25 100644 --- a/SOURCES/0001-Add-initial-redhat-setup.patch +++ b/SOURCES/0001-Add-initial-redhat-setup.patch @@ -1,4 +1,4 @@ -From bfdc177f6127043eac555d356403d9e1d5c52243 Mon Sep 17 00:00:00 2001 +From 4114343d0cd2fc3e5566eed27272480e003c89cc Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Thu, 31 May 2018 16:45:23 +0200 Subject: Add initial redhat setup @@ -7,47 +7,54 @@ Rebase notes (18.5): - added bash_completition file - added cloud-id file +Merged patches (19.4): +- 4ab5a61 Fix for network configuration not persisting after reboot +- 84cf125 Removing cloud-user from wheel +- 31290ab Adding gating tests for Azure, ESXi and AWS + Merged patches (18.5): - 2d6b469 add power-state-change module to cloud_final_modules - 764159f Adding systemd mount options to wait for cloud-init - da4d99e Adding disk_setup to rhel/cloud.cfg - f5c6832 Enable cloud-init by default on vmware - -Signed-off-by: Danilo C. L. de Paula --- + .gitignore | 1 + cloudinit/config/cc_chef.py | 6 +- cloudinit/settings.py | 7 +- redhat/.gitignore | 1 + redhat/Makefile | 71 ++++++ - redhat/Makefile.common | 35 +++ + redhat/Makefile.common | 37 +++ redhat/cloud-init-tmpfiles.conf | 1 + - redhat/cloud-init.spec.template | 352 ++++++++++++++++++++++++++ + redhat/cloud-init.spec.template | 438 ++++++++++++++++++++++++++++++++++ + redhat/gating.yaml | 9 + redhat/rpmbuild/BUILD/.gitignore | 3 + redhat/rpmbuild/RPMS/.gitignore | 3 + redhat/rpmbuild/SOURCES/.gitignore | 3 + redhat/rpmbuild/SPECS/.gitignore | 3 + redhat/rpmbuild/SRPMS/.gitignore | 3 + - redhat/scripts/frh.py | 27 ++ - redhat/scripts/git-backport-diff | 327 ++++++++++++++++++++++++ - redhat/scripts/git-compile-check | 215 ++++++++++++++++ + redhat/scripts/frh.py | 27 +++ + redhat/scripts/git-backport-diff | 327 +++++++++++++++++++++++++ + redhat/scripts/git-compile-check | 215 +++++++++++++++++ redhat/scripts/process-patches.sh | 73 ++++++ redhat/scripts/tarball_checksum.sh | 3 + rhel/README.rhel | 5 + rhel/cloud-init-tmpfiles.conf | 1 + - rhel/cloud.cfg | 69 +++++ + rhel/cloud.cfg | 69 ++++++ rhel/systemd/cloud-config.service | 18 ++ rhel/systemd/cloud-config.target | 11 + rhel/systemd/cloud-final.service | 19 ++ rhel/systemd/cloud-init-local.service | 31 +++ rhel/systemd/cloud-init.service | 25 ++ - setup.py | 64 +---- - tools/read-version | 25 +- - 27 files changed, 1311 insertions(+), 90 deletions(-) + rhel/systemd/cloud-init.target | 7 + + setup.py | 70 +----- + tools/read-version | 28 +-- + 30 files changed, 1417 insertions(+), 98 deletions(-) create mode 100644 redhat/.gitignore create mode 100644 redhat/Makefile create mode 100644 redhat/Makefile.common create mode 100644 redhat/cloud-init-tmpfiles.conf create mode 100644 redhat/cloud-init.spec.template + create mode 100644 redhat/gating.yaml create mode 100644 redhat/rpmbuild/BUILD/.gitignore create mode 100644 redhat/rpmbuild/RPMS/.gitignore create mode 100644 redhat/rpmbuild/SOURCES/.gitignore @@ -66,9 +73,10 @@ Signed-off-by: Danilo C. L. de Paula create mode 100644 rhel/systemd/cloud-final.service create mode 100644 rhel/systemd/cloud-init-local.service create mode 100644 rhel/systemd/cloud-init.service + create mode 100644 rhel/systemd/cloud-init.target diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py -index 46abedd1..fe7bda8c 100644 +index 0ad6b7f..e4408a4 100644 --- a/cloudinit/config/cc_chef.py +++ b/cloudinit/config/cc_chef.py @@ -33,7 +33,7 @@ file). @@ -80,7 +88,7 @@ index 46abedd1..fe7bda8c 100644 validation_cert: (optional string to be written to file validation_key) special value 'system' means set use existing file validation_key: (optional the path for validation_cert. default -@@ -88,7 +88,7 @@ CHEF_DIRS = tuple([ +@@ -89,7 +89,7 @@ CHEF_DIRS = tuple([ '/var/lib/chef', '/var/cache/chef', '/var/backups/chef', @@ -89,20 +97,20 @@ index 46abedd1..fe7bda8c 100644 ]) REQUIRED_CHEF_DIRS = tuple([ '/etc/chef', -@@ -112,7 +112,7 @@ CHEF_RB_TPL_DEFAULTS = { +@@ -113,7 +113,7 @@ CHEF_RB_TPL_DEFAULTS = { 'json_attribs': CHEF_FB_PATH, 'file_cache_path': "/var/cache/chef", 'file_backup_path': "/var/backups/chef", - 'pid_file': "/var/run/chef/client.pid", + 'pid_file': "/run/chef/client.pid", 'show_time': True, + 'encrypted_data_bag_secret': None, } - CHEF_RB_TPL_BOOL_KEYS = frozenset(['show_time']) diff --git a/cloudinit/settings.py b/cloudinit/settings.py -index b1ebaade..c5367687 100644 +index ca4ffa8..3a04a58 100644 --- a/cloudinit/settings.py +++ b/cloudinit/settings.py -@@ -44,13 +44,16 @@ CFG_BUILTIN = { +@@ -46,13 +46,16 @@ CFG_BUILTIN = { ], 'def_log_file': '/var/log/cloud-init.log', 'log_cfgs': [], @@ -123,7 +131,7 @@ index b1ebaade..c5367687 100644 'vendor_data': {'enabled': True, 'prefix': []}, diff --git a/rhel/README.rhel b/rhel/README.rhel new file mode 100644 -index 00000000..aa29630d +index 0000000..aa29630 --- /dev/null +++ b/rhel/README.rhel @@ -0,0 +1,5 @@ @@ -134,14 +142,14 @@ index 00000000..aa29630d + - grub_dpkg diff --git a/rhel/cloud-init-tmpfiles.conf b/rhel/cloud-init-tmpfiles.conf new file mode 100644 -index 00000000..0c6d2a3b +index 0000000..0c6d2a3 --- /dev/null +++ b/rhel/cloud-init-tmpfiles.conf @@ -0,0 +1 @@ +d /run/cloud-init 0700 root root - - diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg new file mode 100644 -index 00000000..f0db3c12 +index 0000000..82e8bf6 --- /dev/null +++ b/rhel/cloud.cfg @@ -0,0 +1,69 @@ @@ -204,7 +212,7 @@ index 00000000..f0db3c12 + name: cloud-user + lock_passwd: true + gecos: Cloud User -+ groups: [wheel, adm, systemd-journal] ++ groups: [adm, systemd-journal] + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + shell: /bin/bash + distro: rhel @@ -216,7 +224,7 @@ index 00000000..f0db3c12 +# vim:syntax=yaml diff --git a/rhel/systemd/cloud-config.service b/rhel/systemd/cloud-config.service new file mode 100644 -index 00000000..12ca9dfd +index 0000000..f3dcd4b --- /dev/null +++ b/rhel/systemd/cloud-config.service @@ -0,0 +1,18 @@ @@ -237,10 +245,10 @@ index 00000000..12ca9dfd +StandardOutput=journal+console + +[Install] -+WantedBy=multi-user.target ++WantedBy=cloud-init.target diff --git a/rhel/systemd/cloud-config.target b/rhel/systemd/cloud-config.target new file mode 100644 -index 00000000..ae9b7d02 +index 0000000..ae9b7d0 --- /dev/null +++ b/rhel/systemd/cloud-config.target @@ -0,0 +1,11 @@ @@ -257,7 +265,7 @@ index 00000000..ae9b7d02 +After=cloud-init-local.service cloud-init.service diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service new file mode 100644 -index 00000000..32a83d85 +index 0000000..739b7e3 --- /dev/null +++ b/rhel/systemd/cloud-final.service @@ -0,0 +1,19 @@ @@ -279,10 +287,10 @@ index 00000000..32a83d85 +StandardOutput=journal+console + +[Install] -+WantedBy=multi-user.target ++WantedBy=cloud-init.target diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service new file mode 100644 -index 00000000..656eddb9 +index 0000000..8f9f6c9 --- /dev/null +++ b/rhel/systemd/cloud-init-local.service @@ -0,0 +1,31 @@ @@ -316,10 +324,10 @@ index 00000000..656eddb9 +StandardOutput=journal+console + +[Install] -+WantedBy=multi-user.target ++WantedBy=cloud-init.target diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service new file mode 100644 -index 00000000..68fc5f19 +index 0000000..d0023a0 --- /dev/null +++ b/rhel/systemd/cloud-init.service @@ -0,0 +1,25 @@ @@ -347,24 +355,40 @@ index 00000000..68fc5f19 +StandardOutput=journal+console + +[Install] -+WantedBy=multi-user.target ++WantedBy=cloud-init.target +diff --git a/rhel/systemd/cloud-init.target b/rhel/systemd/cloud-init.target +new file mode 100644 +index 0000000..083c3b6 +--- /dev/null ++++ b/rhel/systemd/cloud-init.target +@@ -0,0 +1,7 @@ ++# cloud-init target is enabled by cloud-init-generator ++# To disable it you can either: ++# a.) boot with kernel cmdline of 'cloud-init=disabled' ++# b.) touch a file /etc/cloud/cloud-init.disabled ++[Unit] ++Description=Cloud-init target ++After=multi-user.target diff --git a/setup.py b/setup.py -index ea37efc3..06ae48a6 100755 +index 01a67b9..b2ac9bb 100755 --- a/setup.py +++ b/setup.py -@@ -135,11 +135,6 @@ INITSYS_FILES = { +@@ -139,14 +139,6 @@ INITSYS_FILES = { 'sysvinit_deb': [f for f in glob('sysvinit/debian/*') if is_f(f)], 'sysvinit_openrc': [f for f in glob('sysvinit/gentoo/*') if is_f(f)], 'sysvinit_suse': [f for f in glob('sysvinit/suse/*') if is_f(f)], - 'systemd': [render_tmpl(f) - for f in (glob('systemd/*.tmpl') + - glob('systemd/*.service') + -- glob('systemd/*.target')) if is_f(f)], -- 'systemd.generators': [f for f in glob('systemd/*-generator') if is_f(f)], +- glob('systemd/*.target')) +- if (is_f(f) and not is_generator(f))], +- 'systemd.generators': [ +- render_tmpl(f, mode=0o755) +- for f in glob('systemd/*') if is_f(f) and is_generator(f)], 'upstart': [f for f in glob('upstart/*') if is_f(f)], } INITSYS_ROOTS = { -@@ -148,9 +143,6 @@ INITSYS_ROOTS = { +@@ -155,9 +147,6 @@ INITSYS_ROOTS = { 'sysvinit_deb': 'etc/init.d', 'sysvinit_openrc': 'etc/init.d', 'sysvinit_suse': 'etc/init.d', @@ -374,7 +398,7 @@ index ea37efc3..06ae48a6 100755 'upstart': 'etc/init/', } INITSYS_TYPES = sorted([f.partition(".")[0] for f in INITSYS_ROOTS.keys()]) -@@ -188,47 +180,6 @@ class MyEggInfo(egg_info): +@@ -208,47 +197,6 @@ class MyEggInfo(egg_info): return ret @@ -422,20 +446,24 @@ index ea37efc3..06ae48a6 100755 if not in_virtualenv(): USR = "/" + USR ETC = "/" + ETC -@@ -239,11 +190,9 @@ if not in_virtualenv(): +@@ -258,14 +206,11 @@ if not in_virtualenv(): + INITSYS_ROOTS[k] = "/" + INITSYS_ROOTS[k] data_files = [ - (ETC + '/bash_completion.d', ['bash_completion/cloud-init']), - (ETC + '/cloud', [render_tmpl("config/cloud.cfg.tmpl")]), ++ (ETC + '/bash_completion.d', ['bash_completion/cloud-init']), (ETC + '/cloud/cloud.cfg.d', glob('config/cloud.cfg.d/*')), (ETC + '/cloud/templates', glob('templates/*')), - (USR_LIB_EXEC + '/cloud-init', ['tools/ds-identify', - 'tools/uncloud-init', + (USR_LIB_EXEC + '/cloud-init', ['tools/uncloud-init', 'tools/write-ssh-key-fingerprints']), +- (USR + '/share/bash-completion/completions', +- ['bash_completion/cloud-init']), (USR + '/share/doc/cloud-init', [f for f in glob('doc/*') if is_f(f)]), (USR + '/share/doc/cloud-init/examples', -@@ -255,15 +204,8 @@ if os.uname()[0] != 'FreeBSD': + [f for f in glob('doc/examples/*') if is_f(f)]), +@@ -276,15 +221,8 @@ if os.uname()[0] != 'FreeBSD': data_files.extend([ (ETC + '/NetworkManager/dispatcher.d/', ['tools/hook-network-manager']), @@ -452,7 +480,7 @@ index ea37efc3..06ae48a6 100755 requirements = read_requires() -@@ -278,8 +220,6 @@ setuptools.setup( +@@ -299,8 +237,6 @@ setuptools.setup( scripts=['tools/cloud-init-per'], license='Dual-licensed under GPLv3 or Apache 2.0', data_files=data_files, @@ -462,10 +490,10 @@ index ea37efc3..06ae48a6 100755 'console_scripts': [ 'cloud-init = cloudinit.cmd.main:main', diff --git a/tools/read-version b/tools/read-version -index e69c2ce0..d43cc8f0 100755 +index 6dca659..d43cc8f 100755 --- a/tools/read-version +++ b/tools/read-version -@@ -65,29 +65,8 @@ output_json = '--json' in sys.argv +@@ -65,32 +65,8 @@ output_json = '--json' in sys.argv src_version = ci_version.version_string() version_long = None @@ -475,9 +503,12 @@ index e69c2ce0..d43cc8f0 100755 - flags = ['--tags'] - cmd = ['git', 'describe', '--abbrev=8', '--match=[0-9]*'] + flags - -- version = tiny_p(cmd).strip() +- try: +- version = tiny_p(cmd).strip() +- except RuntimeError: +- version = None - -- if not version.startswith(src_version): +- if version is None or not version.startswith(src_version): - sys.stderr.write("git describe version (%s) differs from " - "cloudinit.version (%s)\n" % (version, src_version)) - sys.stderr.write( @@ -498,5 +529,5 @@ index e69c2ce0..d43cc8f0 100755 # version is X.Y.Z[+xxx.gHASH] # version_long is None or X.Y.Z-xxx-gHASH -- -2.20.1 +1.8.3.1 diff --git a/SOURCES/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch b/SOURCES/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch index 1dcf4bd..5b6718a 100644 --- a/SOURCES/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch +++ b/SOURCES/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch @@ -1,271 +1,271 @@ -From 0bff7d73c49043b0820d0231c9a47539287f35e3 Mon Sep 17 00:00:00 2001 +From aa7ae9da7e10a5bcf190f8df3072e3864b2d8fb3 Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Thu, 31 May 2018 19:37:55 +0200 Subject: Do not write NM_CONTROLLED=no in generated interface config files X-downstream-only: true Signed-off-by: Ryan McCabe -Signed-off-by: Danilo C. L. de Paula --- cloudinit/net/sysconfig.py | 1 - tests/unittests/test_net.py | 30 ------------------------------ 2 files changed, 31 deletions(-) diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index 17293e1d..ae0554ef 100644 +index 310cdf0..8bd7e88 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py -@@ -250,7 +250,6 @@ class Renderer(renderer.Renderer): +@@ -272,7 +272,6 @@ class Renderer(renderer.Renderer): iface_defaults = tuple([ ('ONBOOT', True), ('USERCTL', False), - ('NM_CONTROLLED', False), ('BOOTPROTO', 'none'), + ('STARTMODE', 'auto'), ]) - diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 195f261c..5f1aa3e7 100644 +index 01119e0..a931a3e 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py -@@ -175,7 +175,6 @@ GATEWAY=172.19.3.254 +@@ -530,7 +530,6 @@ GATEWAY=172.19.3.254 HWADDR=fa:16:3e:ed:9a:59 IPADDR=172.19.1.34 NETMASK=255.255.252.0 -NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -279,7 +278,6 @@ IPADDR=172.19.1.34 +@@ -636,7 +635,6 @@ IPADDR=172.19.1.34 IPADDR1=10.0.0.10 NETMASK=255.255.252.0 NETMASK1=255.255.255.0 -NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -407,7 +405,6 @@ IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" +@@ -772,7 +770,6 @@ IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" IPV6INIT=yes IPV6_DEFAULTGW=2001:DB8::1 NETMASK=255.255.252.0 -NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -523,7 +520,6 @@ NETWORK_CONFIGS = { +@@ -889,7 +886,6 @@ NETWORK_CONFIGS = { BOOTPROTO=none DEVICE=eth1 HWADDR=cf:d6:af:48:e8:80 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no"""), -@@ -539,7 +535,6 @@ NETWORK_CONFIGS = { +@@ -907,7 +903,6 @@ NETWORK_CONFIGS = { IPADDR=192.168.21.3 NETMASK=255.255.255.0 METRIC=10000 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no"""), -@@ -652,7 +647,6 @@ NETWORK_CONFIGS = { +@@ -1022,7 +1017,6 @@ NETWORK_CONFIGS = { IPV6ADDR=2001:1::1/64 IPV6INIT=yes NETMASK=255.255.255.0 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -894,14 +888,12 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -1491,7 +1485,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true DHCPV6C=yes IPV6INIT=yes MACADDR=aa:bb:cc:dd:ee:ff - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Bond - USERCTL=no"""), - 'ifcfg-bond0.200': textwrap.dedent("""\ +@@ -1500,7 +1493,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true BOOTPROTO=dhcp DEVICE=bond0.200 + DHCLIENT_SET_DEFAULT_ROUTE=no - NM_CONTROLLED=no ONBOOT=yes PHYSDEV=bond0 - TYPE=Ethernet -@@ -918,7 +910,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true + STARTMODE=auto +@@ -1519,7 +1511,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true IPV6_DEFAULTGW=2001:4800:78ff:1b::1 MACADDR=bb:bb:bb:bb:bb:aa NETMASK=255.255.255.0 - NM_CONTROLLED=no ONBOOT=yes PRIO=22 - STP=no -@@ -928,7 +919,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true + STARTMODE=auto +@@ -1530,7 +1521,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true BOOTPROTO=none DEVICE=eth0 HWADDR=c0:d6:9f:2c:e8:80 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no"""), -@@ -945,7 +935,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -1548,7 +1538,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true MTU=1500 NETMASK=255.255.255.0 NETMASK1=255.255.255.0 - NM_CONTROLLED=no ONBOOT=yes PHYSDEV=eth0 - TYPE=Ethernet -@@ -956,7 +945,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true + STARTMODE=auto +@@ -1560,7 +1549,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true DEVICE=eth1 HWADDR=aa:d6:9f:2c:e8:80 MASTER=bond0 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto SLAVE=yes - TYPE=Ethernet -@@ -966,7 +954,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -1571,7 +1559,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true DEVICE=eth2 HWADDR=c0:bb:9f:2c:e8:80 MASTER=bond0 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto SLAVE=yes - TYPE=Ethernet -@@ -976,7 +963,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -1582,7 +1569,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true BRIDGE=br0 DEVICE=eth3 HWADDR=66:bb:9f:2c:e8:80 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no"""), -@@ -985,7 +971,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -1592,7 +1578,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true BRIDGE=br0 DEVICE=eth4 HWADDR=98:bb:9f:2c:e8:80 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no"""), -@@ -993,7 +978,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - BOOTPROTO=dhcp +@@ -1602,7 +1587,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true DEVICE=eth5 + DHCLIENT_SET_DEFAULT_ROUTE=no HWADDR=98:bb:9f:2c:e8:8a - NM_CONTROLLED=no ONBOOT=no + STARTMODE=manual TYPE=Ethernet - USERCTL=no""") -@@ -1356,7 +1340,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -2088,7 +2072,6 @@ iface bond0 inet6 static MTU=9000 NETMASK=255.255.255.0 NETMASK1=255.255.255.0 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Bond - USERCTL=no -@@ -1366,7 +1349,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -2099,7 +2082,6 @@ iface bond0 inet6 static DEVICE=bond0s0 HWADDR=aa:bb:cc:dd:e8:00 MASTER=bond0 - NM_CONTROLLED=no ONBOOT=yes SLAVE=yes - TYPE=Ethernet -@@ -1388,7 +1370,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true + STARTMODE=auto +@@ -2122,7 +2104,6 @@ iface bond0 inet6 static DEVICE=bond0s1 HWADDR=aa:bb:cc:dd:e8:01 MASTER=bond0 - NM_CONTROLLED=no ONBOOT=yes SLAVE=yes - TYPE=Ethernet -@@ -1426,7 +1407,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true + STARTMODE=auto +@@ -2161,7 +2142,6 @@ iface bond0 inet6 static BOOTPROTO=none DEVICE=en0 HWADDR=aa:bb:cc:dd:e8:00 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no"""), -@@ -1443,7 +1423,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -2180,7 +2160,6 @@ iface bond0 inet6 static MTU=2222 NETMASK=255.255.255.0 NETMASK1=255.255.255.0 - NM_CONTROLLED=no ONBOOT=yes PHYSDEV=en0 - TYPE=Ethernet -@@ -1484,7 +1463,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true + STARTMODE=auto +@@ -2222,7 +2201,6 @@ iface bond0 inet6 static DEVICE=br0 IPADDR=192.168.2.2 NETMASK=255.255.255.0 - NM_CONTROLLED=no ONBOOT=yes PRIO=22 - STP=no -@@ -1498,7 +1476,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - HWADDR=52:54:00:12:34:00 + STARTMODE=auto +@@ -2238,7 +2216,6 @@ iface bond0 inet6 static + IPADDR6=2001:1::100/96 IPV6ADDR=2001:1::100/96 IPV6INIT=yes - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -1510,7 +1487,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - HWADDR=52:54:00:12:34:01 +@@ -2252,7 +2229,6 @@ iface bond0 inet6 static + IPADDR6=2001:1::101/96 IPV6ADDR=2001:1::101/96 IPV6INIT=yes - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -1584,7 +1560,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -2327,7 +2303,6 @@ iface bond0 inet6 static HWADDR=52:54:00:12:34:00 IPADDR=192.168.1.2 NETMASK=255.255.255.0 - NM_CONTROLLED=no ONBOOT=no + STARTMODE=manual TYPE=Ethernet - USERCTL=no -@@ -1594,7 +1569,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -2338,7 +2313,6 @@ iface bond0 inet6 static DEVICE=eth1 HWADDR=52:54:00:12:34:aa MTU=1480 - NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -1603,7 +1577,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true +@@ -2348,7 +2322,6 @@ iface bond0 inet6 static BOOTPROTO=none DEVICE=eth2 HWADDR=52:54:00:12:34:ff - NM_CONTROLLED=no ONBOOT=no + STARTMODE=manual TYPE=Ethernet - USERCTL=no -@@ -1969,7 +1942,6 @@ class TestRhelSysConfigRendering(CiTestCase): +@@ -2766,7 +2739,6 @@ class TestRhelSysConfigRendering(CiTestCase): BOOTPROTO=dhcp DEVICE=eth1000 - HWADDR=07-1C-C6-75-A4-BE + HWADDR=07-1c-c6-75-a4-be -NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -2090,7 +2062,6 @@ GATEWAY=10.0.2.2 +@@ -2888,7 +2860,6 @@ GATEWAY=10.0.2.2 HWADDR=52:54:00:12:34:00 IPADDR=10.0.2.15 NETMASK=255.255.255.0 -NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -@@ -2111,7 +2082,6 @@ USERCTL=no +@@ -2961,7 +2932,6 @@ USERCTL=no # BOOTPROTO=dhcp DEVICE=eth0 -NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet - USERCTL=no -- -2.20.1 +1.8.3.1 diff --git a/SOURCES/0003-limit-permissions-on-def_log_file.patch b/SOURCES/0003-limit-permissions-on-def_log_file.patch index 1e63b06..7265152 100644 --- a/SOURCES/0003-limit-permissions-on-def_log_file.patch +++ b/SOURCES/0003-limit-permissions-on-def_log_file.patch @@ -1,4 +1,4 @@ -From fa8f782f5dd24e81f7072bfc24c75340f0972af5 Mon Sep 17 00:00:00 2001 +From f15946568fe731dc9bf477f3f06c9c4e0f74f7c1 Mon Sep 17 00:00:00 2001 From: Lars Kellogg-Stedman Date: Fri, 7 Apr 2017 18:50:54 -0400 Subject: limit permissions on def_log_file @@ -9,7 +9,6 @@ configurable via the def_log_file_mode option in cloud.cfg. LP: #1541196 Resolves: rhbz#1424612 X-approved-upstream: true -Signed-off-by: Danilo C. L. de Paula --- cloudinit/settings.py | 1 + cloudinit/stages.py | 3 ++- @@ -17,10 +16,10 @@ Signed-off-by: Danilo C. L. de Paula 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cloudinit/settings.py b/cloudinit/settings.py -index c5367687..d982a4d6 100644 +index 3a04a58..439eee0 100644 --- a/cloudinit/settings.py +++ b/cloudinit/settings.py -@@ -43,6 +43,7 @@ CFG_BUILTIN = { +@@ -45,6 +45,7 @@ CFG_BUILTIN = { 'None', ], 'def_log_file': '/var/log/cloud-init.log', @@ -29,10 +28,10 @@ index c5367687..d982a4d6 100644 'mount_default_fields': [None, None, 'auto', 'defaults,nofail', '0', '2'], 'ssh_deletekeys': False, diff --git a/cloudinit/stages.py b/cloudinit/stages.py -index 8a064124..4f15484d 100644 +index 71f3a49..68b83af 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py -@@ -148,8 +148,9 @@ class Init(object): +@@ -149,8 +149,9 @@ class Init(object): def _initialize_filesystem(self): util.ensure_dirs(self._initial_subdirs()) log_file = util.get_cfg_option_str(self.cfg, 'def_log_file') @@ -44,7 +43,7 @@ index 8a064124..4f15484d 100644 if not perms: perms = {} diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt -index eb84dcf5..0e82b83e 100644 +index eb84dcf..0e82b83 100644 --- a/doc/examples/cloud-config.txt +++ b/doc/examples/cloud-config.txt @@ -413,10 +413,14 @@ timezone: US/Eastern @@ -63,5 +62,5 @@ index eb84dcf5..0e82b83e 100644 # you can set passwords for a user or multiple users -- -2.20.1 +1.8.3.1 diff --git a/SOURCES/0004-azure-ensure-that-networkmanager-hook-script-runs.patch b/SOURCES/0004-azure-ensure-that-networkmanager-hook-script-runs.patch deleted file mode 100644 index bb78670..0000000 --- a/SOURCES/0004-azure-ensure-that-networkmanager-hook-script-runs.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 8a8af21fc8fff984f2b4285e9993cfd50cad70c4 Mon Sep 17 00:00:00 2001 -From: Lars Kellogg-Stedman -Date: Thu, 15 Jun 2017 12:20:39 -0400 -Subject: azure: ensure that networkmanager hook script runs - -The networkmanager hook script was failing to run due to the changes -we made to resolve rhbz#1440831. This corrects the regression by -allowing the NM hook script to run regardless of whether or not -cloud-init is "enabled". - -Resolves: rhbz#1460206 -X-downstream-only: true -Signed-off-by: Danilo C. L. de Paula ---- - tools/hook-dhclient | 3 +-- - tools/hook-network-manager | 3 +-- - tools/hook-rhel.sh | 3 +-- - 3 files changed, 3 insertions(+), 6 deletions(-) - -diff --git a/tools/hook-dhclient b/tools/hook-dhclient -index 02122f37..181cd51e 100755 ---- a/tools/hook-dhclient -+++ b/tools/hook-dhclient -@@ -13,8 +13,7 @@ is_azure() { - } - - is_enabled() { -- # only execute hooks if cloud-init is enabled and on azure -- [ -e /run/cloud-init/enabled ] || return 1 -+ # only execute hooks if cloud-init is running on azure - is_azure - } - -diff --git a/tools/hook-network-manager b/tools/hook-network-manager -index 67d9044a..1d52cad7 100755 ---- a/tools/hook-network-manager -+++ b/tools/hook-network-manager -@@ -13,8 +13,7 @@ is_azure() { - } - - is_enabled() { -- # only execute hooks if cloud-init is enabled and on azure -- [ -e /run/cloud-init/enabled ] || return 1 -+ # only execute hooks if cloud-init running on azure - is_azure - } - -diff --git a/tools/hook-rhel.sh b/tools/hook-rhel.sh -index 513a5515..d75767e2 100755 ---- a/tools/hook-rhel.sh -+++ b/tools/hook-rhel.sh -@@ -13,8 +13,7 @@ is_azure() { - } - - is_enabled() { -- # only execute hooks if cloud-init is enabled and on azure -- [ -e /run/cloud-init/enabled ] || return 1 -+ # only execute hooks if cloud-init is running on azure - is_azure - } - --- -2.20.1 - diff --git a/SOURCES/0004-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch b/SOURCES/0004-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch new file mode 100644 index 0000000..846a2d1 --- /dev/null +++ b/SOURCES/0004-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch @@ -0,0 +1,34 @@ +From e2b22710db558df261883eaf5dde866c69ba17dd Mon Sep 17 00:00:00 2001 +From: Miroslav Rezanina +Date: Thu, 31 May 2018 20:00:32 +0200 +Subject: sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp + +Don't write BOOTPROTO=dhcp for ipv6 dhcp, as BOOTPROTO applies +only to ipv4. Explicitly write IPV6_AUTOCONF=no for dhcp on ipv6. + +X-downstream-only: yes + +Resolves: rhbz#1519271 +Signed-off-by: Ryan McCabe + +Merged patches (19.4): +- 6444df4 sysconfig: Don't disable IPV6_AUTOCONF +--- + tests/unittests/test_net.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index a931a3e..1306a0f 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -1483,6 +1483,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + BOOTPROTO=none + DEVICE=bond0 + DHCPV6C=yes ++ IPV6_AUTOCONF=no + IPV6INIT=yes + MACADDR=aa:bb:cc:dd:ee:ff + ONBOOT=yes +-- +1.8.3.1 + diff --git a/SOURCES/0005-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch b/SOURCES/0005-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch new file mode 100644 index 0000000..ce6b66e --- /dev/null +++ b/SOURCES/0005-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch @@ -0,0 +1,56 @@ +From 9a09efb49c2d7cade1f0ac309293166c3c2d8d7b Mon Sep 17 00:00:00 2001 +From: Vitaly Kuznetsov +Date: Tue, 17 Apr 2018 13:07:54 +0200 +Subject: DataSourceAzure.py: use hostnamectl to set hostname + +RH-Author: Vitaly Kuznetsov +Message-id: <20180417130754.12918-3-vkuznets@redhat.com> +Patchwork-id: 79659 +O-Subject: [RHEL7.6/7.5.z cloud-init PATCH 2/2] DataSourceAzure.py: use hostnamectl to set hostname +Bugzilla: 1568717 +RH-Acked-by: Eduardo Otubo +RH-Acked-by: Mohammed Gamal +RH-Acked-by: Cathy Avery + +The right way to set hostname in RHEL7 is: + + $ hostnamectl set-hostname HOSTNAME + +DataSourceAzure, however, uses: + $ hostname HOSTSNAME + +instead and this causes problems. We can't simply change +'BUILTIN_DS_CONFIG' in DataSourceAzure.py as 'hostname' is being used +for both getting and setting the hostname. + +Long term, this should be fixed in a different way. Cloud-init +has distro-specific hostname setting/getting (see +cloudinit/distros/rhel.py) and DataSourceAzure.py needs to be switched +to use these. + +Resolves: rhbz#1434109 + +X-downstream-only: yes + +Signed-off-by: Vitaly Kuznetsov +Signed-off-by: Miroslav Rezanina +--- + cloudinit/sources/DataSourceAzure.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py +index 24f448c..6fb889c 100755 +--- a/cloudinit/sources/DataSourceAzure.py ++++ b/cloudinit/sources/DataSourceAzure.py +@@ -256,7 +256,7 @@ def get_hostname(hostname_command='hostname'): + + + def set_hostname(hostname, hostname_command='hostname'): +- util.subp([hostname_command, hostname]) ++ util.subp(['hostnamectl', 'set-hostname', str(hostname)]) + + + @azure_ds_telemetry_reporter +-- +1.8.3.1 + diff --git a/SOURCES/0005-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch b/SOURCES/0005-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch deleted file mode 100644 index f157e21..0000000 --- a/SOURCES/0005-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 471353b3c3bf5cba5cab4d1b203b1c259c709fde Mon Sep 17 00:00:00 2001 -From: Miroslav Rezanina -Date: Thu, 31 May 2018 20:00:32 +0200 -Subject: sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp - -Don't write BOOTPROTO=dhcp for ipv6 dhcp, as BOOTPROTO applies -only to ipv4. Explicitly write IPV6_AUTOCONF=no for dhcp on ipv6. - -X-downstream-only: yes - -Resolves: rhbz#1519271 -Signed-off-by: Ryan McCabe -Signed-off-by: Danilo C. L. de Paula ---- - cloudinit/net/sysconfig.py | 1 + - tests/unittests/test_net.py | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index ae0554ef..ec166cf1 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -310,6 +310,7 @@ class Renderer(renderer.Renderer): - if subnet_type == 'dhcp6': - iface_cfg['IPV6INIT'] = True - iface_cfg['DHCPV6C'] = True -+ iface_cfg['IPV6_AUTOCONF'] = False - elif subnet_type in ['dhcp4', 'dhcp']: - iface_cfg['BOOTPROTO'] = 'dhcp' - elif subnet_type == 'static': -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 5f1aa3e7..8bcafe08 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -886,6 +886,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - BOOTPROTO=none - DEVICE=bond0 - DHCPV6C=yes -+ IPV6_AUTOCONF=no - IPV6INIT=yes - MACADDR=aa:bb:cc:dd:ee:ff - ONBOOT=yes --- -2.20.1 - diff --git a/SOURCES/0006-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch b/SOURCES/0006-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch deleted file mode 100644 index 0da5464..0000000 --- a/SOURCES/0006-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 21ea1cda0055416119edea44de95b5606f0b0e15 Mon Sep 17 00:00:00 2001 -From: Vitaly Kuznetsov -Date: Tue, 17 Apr 2018 13:07:54 +0200 -Subject: DataSourceAzure.py: use hostnamectl to set hostname - -RH-Author: Vitaly Kuznetsov -Message-id: <20180417130754.12918-3-vkuznets@redhat.com> -Patchwork-id: 79659 -O-Subject: [RHEL7.6/7.5.z cloud-init PATCH 2/2] DataSourceAzure.py: use hostnamectl to set hostname -Bugzilla: 1568717 -RH-Acked-by: Eduardo Otubo -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Cathy Avery - -The right way to set hostname in RHEL7 is: - - $ hostnamectl set-hostname HOSTNAME - -DataSourceAzure, however, uses: - $ hostname HOSTSNAME - -instead and this causes problems. We can't simply change -'BUILTIN_DS_CONFIG' in DataSourceAzure.py as 'hostname' is being used -for both getting and setting the hostname. - -Long term, this should be fixed in a different way. Cloud-init -has distro-specific hostname setting/getting (see -cloudinit/distros/rhel.py) and DataSourceAzure.py needs to be switched -to use these. - -Resolves: rhbz#1434109 - -X-downstream-only: yes - -Signed-off-by: Vitaly Kuznetsov -Signed-off-by: Miroslav Rezanina -Signed-off-by: Danilo C. L. de Paula ---- - cloudinit/sources/DataSourceAzure.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -index e076d5dc..7dbeb04c 100644 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -238,7 +238,7 @@ def get_hostname(hostname_command='hostname'): - - - def set_hostname(hostname, hostname_command='hostname'): -- util.subp([hostname_command, hostname]) -+ util.subp(['hostnamectl', 'set-hostname', str(hostname)]) - - - @contextlib.contextmanager --- -2.20.1 - diff --git a/SOURCES/0006-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch b/SOURCES/0006-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch new file mode 100644 index 0000000..1dff33f --- /dev/null +++ b/SOURCES/0006-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch @@ -0,0 +1,65 @@ +From 13ee71a3add0dd2e7c60fc672134e696bd7f6a77 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Wed, 20 Mar 2019 11:45:59 +0100 +Subject: include 'NOZEROCONF=yes' in /etc/sysconfig/network + +RH-Author: Eduardo Otubo +Message-id: <20190320114559.23708-1-otubo@redhat.com> +Patchwork-id: 84937 +O-Subject: [RHEL-7.7 cloud-init PATCH] include 'NOZEROCONF=yes' in /etc/sysconfig/network +Bugzilla: 1653131 +RH-Acked-by: Cathy Avery +RH-Acked-by: Mohammed Gamal +RH-Acked-by: Vitaly Kuznetsov + +The option NOZEROCONF=yes is not included by default in +/etc/sysconfig/network, which is required by Overcloud instances. The +patch also includes tests for the modifications. + +X-downstream-only: yes +Resolves: rhbz#1653131 + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + cloudinit/net/sysconfig.py | 11 ++++++++++- + tests/unittests/test_net.py | 1 - + 2 files changed, 10 insertions(+), 2 deletions(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index 8bd7e88..810b283 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -754,7 +754,16 @@ class Renderer(renderer.Renderer): + # Distros configuring /etc/sysconfig/network as a file e.g. Centos + if sysconfig_path.endswith('network'): + util.ensure_dir(os.path.dirname(sysconfig_path)) +- netcfg = [_make_header(), 'NETWORKING=yes'] ++ netcfg = [] ++ for line in util.load_file(sysconfig_path, quiet=True).split('\n'): ++ if 'cloud-init' in line: ++ break ++ if not line.startswith(('NETWORKING=', ++ 'IPV6_AUTOCONF=', ++ 'NETWORKING_IPV6=')): ++ netcfg.append(line) ++ # Now generate the cloud-init portion of sysconfig/network ++ netcfg.extend([_make_header(), 'NETWORKING=yes']) + if network_state.use_ipv6: + netcfg.append('NETWORKING_IPV6=yes') + netcfg.append('IPV6_AUTOCONF=no') +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 1306a0f..a931a3e 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -1483,7 +1483,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + BOOTPROTO=none + DEVICE=bond0 + DHCPV6C=yes +- IPV6_AUTOCONF=no + IPV6INIT=yes + MACADDR=aa:bb:cc:dd:ee:ff + ONBOOT=yes +-- +1.8.3.1 + diff --git a/SOURCES/0007-Remove-race-condition-between-cloud-init-and-Network.patch b/SOURCES/0007-Remove-race-condition-between-cloud-init-and-Network.patch new file mode 100644 index 0000000..0e6eb1f --- /dev/null +++ b/SOURCES/0007-Remove-race-condition-between-cloud-init-and-Network.patch @@ -0,0 +1,56 @@ +From 9d951d55a1be44bbeb5df485d14d4f84ddf01142 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Mon, 2 Mar 2020 10:46:35 +0100 +Subject: Remove race condition between cloud-init and NetworkManager + +Message-id: <20200302104635.11648-1-otubo@redhat.com> +Patchwork-id: 94098 +O-Subject: [RHEL-7.9/RHEL-8.2.0 cloud-init PATCH] Remove race condition between cloud-init and NetworkManager +Bugzilla: 1807797 +RH-Acked-by: Cathy Avery +RH-Acked-by: Mohammed Gamal + +BZ: 1748015 +BRANCH: rhel7/master-18.5 +BREW: 26924611 + +BZ: 1807797 +BRANCH: rhel820/master-18.5 +BREW: 26924957 + +cloud-init service is set to start before NetworkManager service starts, +but this does not avoid a race condition between them. NetworkManager +starts before cloud-init can write `dns=none' to the file: +/etc/NetworkManager/conf.d/99-cloud-init.conf. This way NetworkManager +doesn't read the configuration and erases all resolv.conf values upon +shutdown. On the next reboot neither cloud-init or NetworkManager will +write anything to resolv.conf, leaving it blank. + +This patch introduces a NM reload (try-restart) at the end of cloud-init +start up so it won't erase resolv.conf upon first shutdown. + +x-downstream-only: yes +resolves: rhbz#1748015, rhbz#1807797 and rhbz#1804780 + +Signed-off-by: Eduardo Otubo otubo@redhat.com +Signed-off-by: Miroslav Rezanina +--- + rhel/systemd/cloud-final.service | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service +index 739b7e3..f303483 100644 +--- a/rhel/systemd/cloud-final.service ++++ b/rhel/systemd/cloud-final.service +@@ -11,6 +11,8 @@ ExecStart=/usr/bin/cloud-init modules --mode=final + RemainAfterExit=yes + TimeoutSec=0 + KillMode=process ++ExecStartPost=/bin/echo "try restart NetworkManager.service" ++ExecStartPost=/usr/bin/systemctl try-restart NetworkManager.service + + # Output needs to appear in instance console output + StandardOutput=journal+console +-- +1.8.3.1 + diff --git a/SOURCES/0007-sysconfig-Don-t-disable-IPV6_AUTOCONF.patch b/SOURCES/0007-sysconfig-Don-t-disable-IPV6_AUTOCONF.patch deleted file mode 100644 index 33de1b6..0000000 --- a/SOURCES/0007-sysconfig-Don-t-disable-IPV6_AUTOCONF.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 6444df4c91c611c65bb292e75e2726f767edcf2b Mon Sep 17 00:00:00 2001 -From: Vitaly Kuznetsov -Date: Thu, 26 Apr 2018 09:27:49 +0200 -Subject: sysconfig: Don't disable IPV6_AUTOCONF - -RH-Author: Vitaly Kuznetsov -Message-id: <20180426092749.7251-2-vkuznets@redhat.com> -Patchwork-id: 79904 -O-Subject: [RHEL7.6/7.5.z cloud-init PATCH 1/1] sysconfig: Don't disable IPV6_AUTOCONF -Bugzilla: 1578702 -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Cathy Avery -RH-Acked-by: Eduardo Otubo - -Downstream-only commit 118458a3fb ("sysconfig: Don't write BOOTPROTO=dhcp -for ipv6 dhcp") did two things: -1) Disabled BOOTPROTO='dhcp' for dhcp6 setups. This change seems to be - correct as BOOTPROTO is unrelated to IPv6. The change was since merged - upstream (commit a57928d3c314d9568712cd190cb1e721e14c108b). -2) Explicitly disabled AUTOCONF and this broke many valid configurations - using it instead of DHCPV6C. Revert this part of the change. In case - DHCPV6C-only support is needed something like a new 'dhcpv6c_only' - network type needs to be suggested upstream. - -X-downstream-only: yes - -Resolves: rhbz#1558854 - -Signed-off-by: Vitaly Kuznetsov -Signed-off-by: Miroslav Rezanina -Signed-off-by: Danilo C. L. de Paula ---- - cloudinit/net/sysconfig.py | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index ec166cf1..ae0554ef 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -310,7 +310,6 @@ class Renderer(renderer.Renderer): - if subnet_type == 'dhcp6': - iface_cfg['IPV6INIT'] = True - iface_cfg['DHCPV6C'] = True -- iface_cfg['IPV6_AUTOCONF'] = False - elif subnet_type in ['dhcp4', 'dhcp']: - iface_cfg['BOOTPROTO'] = 'dhcp' - elif subnet_type == 'static': --- -2.20.1 - diff --git a/SOURCES/0008-net-Make-sysconfig-renderer-compatible-with-Network-.patch b/SOURCES/0008-net-Make-sysconfig-renderer-compatible-with-Network-.patch deleted file mode 100644 index be1644e..0000000 --- a/SOURCES/0008-net-Make-sysconfig-renderer-compatible-with-Network-.patch +++ /dev/null @@ -1,217 +0,0 @@ -From 86bd1e20fc802edfb920fa53bd611d469f83250b Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Fri, 18 Jan 2019 16:55:36 +0100 -Subject: net: Make sysconfig renderer compatible with Network Manager. - -RH-Author: Eduardo Otubo -Message-id: <20190118165536.25963-1-otubo@redhat.com> -Patchwork-id: 84052 -O-Subject: [RHEL-8.0 cloud-init PATCH] net: Make sysconfig renderer compatible with Network Manager. -Bugzilla: 1602784 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Mohammed Gamal - -Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1602784 -Brew: https://brewweb.engineering.redhat.com/brew/taskinfo?taskID=19877292 -Tested by: upstream maintainers and me - -commit 3861102fcaf47a882516d8b6daab518308eb3086 -Author: Eduardo Otubo -Date: Fri Jan 18 15:36:19 2019 +0000 - - net: Make sysconfig renderer compatible with Network Manager. - - The 'sysconfig' renderer is activated if, and only if, there's ifup and - ifdown commands present in its search dictonary or the network-scripts - configuration files are found. This patch adds a check for Network- - Manager configuration file as well. - - This solution is based on the use of the plugin 'ifcfg-rh' present in - Network-Manager and is designed to support Fedora 29 or other - distributions that also replaced network-scripts by Network-Manager. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina -Signed-off-by: Danilo C. L. de Paula ---- - cloudinit/net/sysconfig.py | 36 +++++++++++++++++++ - tests/unittests/test_net.py | 71 +++++++++++++++++++++++++++++++++++++ - 2 files changed, 107 insertions(+) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index ae0554ef..dc1815d9 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -10,11 +10,14 @@ from cloudinit.distros.parsers import resolv_conf - from cloudinit import log as logging - from cloudinit import util - -+from configobj import ConfigObj -+ - from . import renderer - from .network_state import ( - is_ipv6_addr, net_prefix_to_ipv4_mask, subnet_is_ipv6) - - LOG = logging.getLogger(__name__) -+NM_CFG_FILE = "/etc/NetworkManager/NetworkManager.conf" - - - def _make_header(sep='#'): -@@ -46,6 +49,24 @@ def _quote_value(value): - return value - - -+def enable_ifcfg_rh(path): -+ """Add ifcfg-rh to NetworkManager.cfg plugins if main section is present""" -+ config = ConfigObj(path) -+ if 'main' in config: -+ if 'plugins' in config['main']: -+ if 'ifcfg-rh' in config['main']['plugins']: -+ return -+ else: -+ config['main']['plugins'] = [] -+ -+ if isinstance(config['main']['plugins'], list): -+ config['main']['plugins'].append('ifcfg-rh') -+ else: -+ config['main']['plugins'] = [config['main']['plugins'], 'ifcfg-rh'] -+ config.write() -+ LOG.debug('Enabled ifcfg-rh NetworkManager plugins') -+ -+ - class ConfigMap(object): - """Sysconfig like dictionary object.""" - -@@ -656,6 +677,8 @@ class Renderer(renderer.Renderer): - netrules_content = self._render_persistent_net(network_state) - netrules_path = util.target_path(target, self.netrules_path) - util.write_file(netrules_path, netrules_content, file_mode) -+ if available_nm(target=target): -+ enable_ifcfg_rh(util.target_path(target, path=NM_CFG_FILE)) - - sysconfig_path = util.target_path(target, templates.get('control')) - # Distros configuring /etc/sysconfig/network as a file e.g. Centos -@@ -670,6 +693,13 @@ class Renderer(renderer.Renderer): - - - def available(target=None): -+ sysconfig = available_sysconfig(target=target) -+ nm = available_nm(target=target) -+ -+ return any([nm, sysconfig]) -+ -+ -+def available_sysconfig(target=None): - expected = ['ifup', 'ifdown'] - search = ['/sbin', '/usr/sbin'] - for p in expected: -@@ -685,4 +715,10 @@ def available(target=None): - return True - - -+def available_nm(target=None): -+ if not os.path.isfile(util.target_path(target, path=NM_CFG_FILE)): -+ return False -+ return True -+ -+ - # vi: ts=4 expandtab -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 8bcafe08..526a30ed 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -22,6 +22,7 @@ import os - import textwrap - import yaml - -+ - DHCP_CONTENT_1 = """ - DEVICE='eth0' - PROTO='dhcp' -@@ -1854,6 +1855,7 @@ class TestRhelSysConfigRendering(CiTestCase): - - with_logs = True - -+ nm_cfg_file = "/etc/NetworkManager/NetworkManager.conf" - scripts_dir = '/etc/sysconfig/network-scripts' - header = ('# Created by cloud-init on instance boot automatically, ' - 'do not edit.\n#\n') -@@ -2497,6 +2499,75 @@ iface eth0 inet dhcp - self.assertEqual( - expected, dir2dict(tmp_dir)['/etc/network/interfaces']) - -+ def test_check_ifcfg_rh(self): -+ """ifcfg-rh plugin is added NetworkManager.conf if conf present.""" -+ render_dir = self.tmp_dir() -+ nm_cfg = util.target_path(render_dir, path=self.nm_cfg_file) -+ util.ensure_dir(os.path.dirname(nm_cfg)) -+ -+ # write a template nm.conf, note plugins is a list here -+ with open(nm_cfg, 'w') as fh: -+ fh.write('# test_check_ifcfg_rh\n[main]\nplugins=foo,bar\n') -+ self.assertTrue(os.path.exists(nm_cfg)) -+ -+ # render and read -+ entry = NETWORK_CONFIGS['small'] -+ found = self._render_and_read(network_config=yaml.load(entry['yaml']), -+ dir=render_dir) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) -+ -+ # check ifcfg-rh is in the 'plugins' list -+ config = sysconfig.ConfigObj(nm_cfg) -+ self.assertIn('ifcfg-rh', config['main']['plugins']) -+ -+ def test_check_ifcfg_rh_plugins_string(self): -+ """ifcfg-rh plugin is append when plugins is a string.""" -+ render_dir = self.tmp_path("render") -+ os.makedirs(render_dir) -+ nm_cfg = util.target_path(render_dir, path=self.nm_cfg_file) -+ util.ensure_dir(os.path.dirname(nm_cfg)) -+ -+ # write a template nm.conf, note plugins is a value here -+ util.write_file(nm_cfg, '# test_check_ifcfg_rh\n[main]\nplugins=foo\n') -+ -+ # render and read -+ entry = NETWORK_CONFIGS['small'] -+ found = self._render_and_read(network_config=yaml.load(entry['yaml']), -+ dir=render_dir) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) -+ -+ # check raw content has plugin -+ nm_file_content = util.load_file(nm_cfg) -+ self.assertIn('ifcfg-rh', nm_file_content) -+ -+ # check ifcfg-rh is in the 'plugins' list -+ config = sysconfig.ConfigObj(nm_cfg) -+ self.assertIn('ifcfg-rh', config['main']['plugins']) -+ -+ def test_check_ifcfg_rh_plugins_no_plugins(self): -+ """enable_ifcfg_plugin creates plugins value if missing.""" -+ render_dir = self.tmp_path("render") -+ os.makedirs(render_dir) -+ nm_cfg = util.target_path(render_dir, path=self.nm_cfg_file) -+ util.ensure_dir(os.path.dirname(nm_cfg)) -+ -+ # write a template nm.conf, note plugins is missing -+ util.write_file(nm_cfg, '# test_check_ifcfg_rh\n[main]\n') -+ self.assertTrue(os.path.exists(nm_cfg)) -+ -+ # render and read -+ entry = NETWORK_CONFIGS['small'] -+ found = self._render_and_read(network_config=yaml.load(entry['yaml']), -+ dir=render_dir) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) -+ -+ # check ifcfg-rh is in the 'plugins' list -+ config = sysconfig.ConfigObj(nm_cfg) -+ self.assertIn('ifcfg-rh', config['main']['plugins']) -+ - - class TestNetplanNetRendering(CiTestCase): - --- -2.20.1 - diff --git a/SOURCES/0009-net-Wait-for-dhclient-to-daemonize-before-reading-le.patch b/SOURCES/0009-net-Wait-for-dhclient-to-daemonize-before-reading-le.patch deleted file mode 100644 index 41ba44b..0000000 --- a/SOURCES/0009-net-Wait-for-dhclient-to-daemonize-before-reading-le.patch +++ /dev/null @@ -1,296 +0,0 @@ -From 2e070086275341dfceb6d5b1e12f06f22e7bbfcd Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 23 Jan 2019 12:30:21 +0100 -Subject: net: Wait for dhclient to daemonize before reading lease file - -RH-Author: Eduardo Otubo -Message-id: <20190123123021.32708-1-otubo@redhat.com> -Patchwork-id: 84095 -O-Subject: [RHEL-7.7 cloud-init PATCH] net: Wait for dhclient to daemonize before reading lease file -Bugzilla: 1632967 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Miroslav Rezanina - -Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1632967 -Brew: https://bugzilla.redhat.com/show_bug.cgi?id=1632967 -Tested: Me and upstream - -commit fdadcb5fae51f4e6799314ab98e3aec56c79b17c -Author: Jason Zions -Date: Tue Jan 15 21:37:17 2019 +0000 - - net: Wait for dhclient to daemonize before reading lease file - - cloud-init uses dhclient to fetch the DHCP lease so it can extract - DHCP options. dhclient creates the leasefile, then writes to it; - simply waiting for the leasefile to appear creates a race between - dhclient and cloud-init. Instead, wait for dhclient to be parented by - init. At that point, we know it has written to the leasefile, so it's - safe to copy the file and kill the process. - - cloud-init creates a temporary directory in which to execute dhclient, - and deletes that directory after it has killed the process. If - cloud-init abandons waiting for dhclient to daemonize, it will still - attempt to delete the temporary directory, but will not report an - exception should that attempt fail. - - LP: #1794399 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina -Signed-off-by: Danilo C. L. de Paula ---- - cloudinit/net/dhcp.py | 44 +++++++++++++++++++++--------- - cloudinit/net/tests/test_dhcp.py | 15 ++++++++-- - cloudinit/temp_utils.py | 4 +-- - cloudinit/tests/test_temp_utils.py | 18 +++++++++++- - cloudinit/util.py | 16 ++++++++++- - tests/unittests/test_util.py | 6 ++++ - 6 files changed, 83 insertions(+), 20 deletions(-) - -diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py -index 0db991db..c98a97cd 100644 ---- a/cloudinit/net/dhcp.py -+++ b/cloudinit/net/dhcp.py -@@ -9,6 +9,7 @@ import logging - import os - import re - import signal -+import time - - from cloudinit.net import ( - EphemeralIPv4Network, find_fallback_nic, get_devicelist, -@@ -127,7 +128,9 @@ def maybe_perform_dhcp_discovery(nic=None): - if not dhclient_path: - LOG.debug('Skip dhclient configuration: No dhclient command found.') - return [] -- with temp_utils.tempdir(prefix='cloud-init-dhcp-', needs_exe=True) as tdir: -+ with temp_utils.tempdir(rmtree_ignore_errors=True, -+ prefix='cloud-init-dhcp-', -+ needs_exe=True) as tdir: - # Use /var/tmp because /run/cloud-init/tmp is mounted noexec - return dhcp_discovery(dhclient_path, nic, tdir) - -@@ -195,24 +198,39 @@ def dhcp_discovery(dhclient_cmd_path, interface, cleandir): - '-pf', pid_file, interface, '-sf', '/bin/true'] - util.subp(cmd, capture=True) - -- # dhclient doesn't write a pid file until after it forks when it gets a -- # proper lease response. Since cleandir is a temp directory that gets -- # removed, we need to wait for that pidfile creation before the -- # cleandir is removed, otherwise we get FileNotFound errors. -+ # Wait for pid file and lease file to appear, and for the process -+ # named by the pid file to daemonize (have pid 1 as its parent). If we -+ # try to read the lease file before daemonization happens, we might try -+ # to read it before the dhclient has actually written it. We also have -+ # to wait until the dhclient has become a daemon so we can be sure to -+ # kill the correct process, thus freeing cleandir to be deleted back -+ # up the callstack. - missing = util.wait_for_files( - [pid_file, lease_file], maxwait=5, naplen=0.01) - if missing: - LOG.warning("dhclient did not produce expected files: %s", - ', '.join(os.path.basename(f) for f in missing)) - return [] -- pid_content = util.load_file(pid_file).strip() -- try: -- pid = int(pid_content) -- except ValueError: -- LOG.debug( -- "pid file contains non-integer content '%s'", pid_content) -- else: -- os.kill(pid, signal.SIGKILL) -+ -+ ppid = 'unknown' -+ for _ in range(0, 1000): -+ pid_content = util.load_file(pid_file).strip() -+ try: -+ pid = int(pid_content) -+ except ValueError: -+ pass -+ else: -+ ppid = util.get_proc_ppid(pid) -+ if ppid == 1: -+ LOG.debug('killing dhclient with pid=%s', pid) -+ os.kill(pid, signal.SIGKILL) -+ return parse_dhcp_lease_file(lease_file) -+ time.sleep(0.01) -+ -+ LOG.error( -+ 'dhclient(pid=%s, parentpid=%s) failed to daemonize after %s seconds', -+ pid_content, ppid, 0.01 * 1000 -+ ) - return parse_dhcp_lease_file(lease_file) - - -diff --git a/cloudinit/net/tests/test_dhcp.py b/cloudinit/net/tests/test_dhcp.py -index cd3e7328..79e8842f 100644 ---- a/cloudinit/net/tests/test_dhcp.py -+++ b/cloudinit/net/tests/test_dhcp.py -@@ -145,16 +145,20 @@ class TestDHCPDiscoveryClean(CiTestCase): - 'subnet-mask': '255.255.255.0', 'routers': '192.168.2.1'}], - dhcp_discovery(dhclient_script, 'eth9', tmpdir)) - self.assertIn( -- "pid file contains non-integer content ''", self.logs.getvalue()) -+ "dhclient(pid=, parentpid=unknown) failed " -+ "to daemonize after 10.0 seconds", -+ self.logs.getvalue()) - m_kill.assert_not_called() - -+ @mock.patch('cloudinit.net.dhcp.util.get_proc_ppid') - @mock.patch('cloudinit.net.dhcp.os.kill') - @mock.patch('cloudinit.net.dhcp.util.wait_for_files') - @mock.patch('cloudinit.net.dhcp.util.subp') - def test_dhcp_discovery_run_in_sandbox_waits_on_lease_and_pid(self, - m_subp, - m_wait, -- m_kill): -+ m_kill, -+ m_getppid): - """dhcp_discovery waits for the presence of pidfile and dhcp.leases.""" - tmpdir = self.tmp_dir() - dhclient_script = os.path.join(tmpdir, 'dhclient.orig') -@@ -164,6 +168,7 @@ class TestDHCPDiscoveryClean(CiTestCase): - pidfile = self.tmp_path('dhclient.pid', tmpdir) - leasefile = self.tmp_path('dhcp.leases', tmpdir) - m_wait.return_value = [pidfile] # Return the missing pidfile wait for -+ m_getppid.return_value = 1 # Indicate that dhclient has daemonized - self.assertEqual([], dhcp_discovery(dhclient_script, 'eth9', tmpdir)) - self.assertEqual( - mock.call([pidfile, leasefile], maxwait=5, naplen=0.01), -@@ -173,9 +178,10 @@ class TestDHCPDiscoveryClean(CiTestCase): - self.logs.getvalue()) - m_kill.assert_not_called() - -+ @mock.patch('cloudinit.net.dhcp.util.get_proc_ppid') - @mock.patch('cloudinit.net.dhcp.os.kill') - @mock.patch('cloudinit.net.dhcp.util.subp') -- def test_dhcp_discovery_run_in_sandbox(self, m_subp, m_kill): -+ def test_dhcp_discovery_run_in_sandbox(self, m_subp, m_kill, m_getppid): - """dhcp_discovery brings up the interface and runs dhclient. - - It also returns the parsed dhcp.leases file generated in the sandbox. -@@ -197,6 +203,7 @@ class TestDHCPDiscoveryClean(CiTestCase): - pid_file = os.path.join(tmpdir, 'dhclient.pid') - my_pid = 1 - write_file(pid_file, "%d\n" % my_pid) -+ m_getppid.return_value = 1 # Indicate that dhclient has daemonized - - self.assertItemsEqual( - [{'interface': 'eth9', 'fixed-address': '192.168.2.74', -@@ -355,3 +362,5 @@ class TestEphemeralDhcpNoNetworkSetup(HttprettyTestCase): - self.assertEqual(fake_lease, lease) - # Ensure that dhcp discovery occurs - m_dhcp.called_once_with() -+ -+# vi: ts=4 expandtab -diff --git a/cloudinit/temp_utils.py b/cloudinit/temp_utils.py -index c98a1b53..346276ec 100644 ---- a/cloudinit/temp_utils.py -+++ b/cloudinit/temp_utils.py -@@ -81,7 +81,7 @@ def ExtendedTemporaryFile(**kwargs): - - - @contextlib.contextmanager --def tempdir(**kwargs): -+def tempdir(rmtree_ignore_errors=False, **kwargs): - # This seems like it was only added in python 3.2 - # Make it since its useful... - # See: http://bugs.python.org/file12970/tempdir.patch -@@ -89,7 +89,7 @@ def tempdir(**kwargs): - try: - yield tdir - finally: -- shutil.rmtree(tdir) -+ shutil.rmtree(tdir, ignore_errors=rmtree_ignore_errors) - - - def mkdtemp(**kwargs): -diff --git a/cloudinit/tests/test_temp_utils.py b/cloudinit/tests/test_temp_utils.py -index ffbb92cd..4a52ef89 100644 ---- a/cloudinit/tests/test_temp_utils.py -+++ b/cloudinit/tests/test_temp_utils.py -@@ -2,8 +2,9 @@ - - """Tests for cloudinit.temp_utils""" - --from cloudinit.temp_utils import mkdtemp, mkstemp -+from cloudinit.temp_utils import mkdtemp, mkstemp, tempdir - from cloudinit.tests.helpers import CiTestCase, wrap_and_call -+import os - - - class TestTempUtils(CiTestCase): -@@ -98,4 +99,19 @@ class TestTempUtils(CiTestCase): - self.assertEqual('/fake/return/path', retval) - self.assertEqual([{'dir': '/run/cloud-init/tmp'}], calls) - -+ def test_tempdir_error_suppression(self): -+ """test tempdir suppresses errors during directory removal.""" -+ -+ with self.assertRaises(OSError): -+ with tempdir(prefix='cloud-init-dhcp-') as tdir: -+ os.rmdir(tdir) -+ # As a result, the directory is already gone, -+ # so shutil.rmtree should raise OSError -+ -+ with tempdir(rmtree_ignore_errors=True, -+ prefix='cloud-init-dhcp-') as tdir: -+ os.rmdir(tdir) -+ # Since the directory is already gone, shutil.rmtree would raise -+ # OSError, but we suppress that -+ - # vi: ts=4 expandtab -diff --git a/cloudinit/util.py b/cloudinit/util.py -index 7800f7bc..a84112a9 100644 ---- a/cloudinit/util.py -+++ b/cloudinit/util.py -@@ -2861,7 +2861,6 @@ def mount_is_read_write(mount_point): - mount_opts = result[-1].split(',') - return mount_opts[0] == 'rw' - -- - def udevadm_settle(exists=None, timeout=None): - """Invoke udevadm settle with optional exists and timeout parameters""" - settle_cmd = ["udevadm", "settle"] -@@ -2875,5 +2874,20 @@ def udevadm_settle(exists=None, timeout=None): - - return subp(settle_cmd) - -+def get_proc_ppid(pid): -+ """ -+ Return the parent pid of a process. -+ """ -+ ppid = 0 -+ try: -+ contents = load_file("/proc/%s/stat" % pid, quiet=True) -+ except IOError as e: -+ LOG.warning('Failed to load /proc/%s/stat. %s', pid, e) -+ if contents: -+ parts = contents.split(" ", 4) -+ # man proc says -+ # ppid %d (4) The PID of the parent. -+ ppid = int(parts[3]) -+ return ppid - - # vi: ts=4 expandtab -diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py -index 5a14479a..8aebcd62 100644 ---- a/tests/unittests/test_util.py -+++ b/tests/unittests/test_util.py -@@ -1114,6 +1114,12 @@ class TestLoadShellContent(helpers.TestCase): - 'key3="val3 #tricky"', - '']))) - -+ def test_get_proc_ppid(self): -+ """get_proc_ppid returns correct parent pid value.""" -+ my_pid = os.getpid() -+ my_ppid = os.getppid() -+ self.assertEqual(my_ppid, util.get_proc_ppid(my_pid)) -+ - - class TestGetProcEnv(helpers.TestCase): - """test get_proc_env.""" --- -2.20.1 - diff --git a/SOURCES/0010-cloud-init-per-don-t-use-dashes-in-sem-names.patch b/SOURCES/0010-cloud-init-per-don-t-use-dashes-in-sem-names.patch deleted file mode 100644 index f356066..0000000 --- a/SOURCES/0010-cloud-init-per-don-t-use-dashes-in-sem-names.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 8a3bf53398f312b46ed4f304df4c66d061e612c7 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Thu, 28 Feb 2019 12:38:36 +0100 -Subject: cloud-init-per: don't use dashes in sem names - -RH-Author: Eduardo Otubo -Message-id: <20190228123836.17979-1-otubo@redhat.com> -Patchwork-id: 84743 -O-Subject: [RHEL-7.7 cloud-init PATCH] This is to fix https://bugs.launchpad.net/cloud-init/+bug/1812676 -Bugzilla: 1664876 -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Vitaly Kuznetsov - -From: Vitaly Kuznetsov - - It was found that when there is a dash in cloud-init-per command - name and cloud-init-per is executed through cloud-init's bootcmd, e.g: - - bootcmd: - - cloud-init-per instance mycmd-bootcmd /usr/bin/mycmd - - the command is executed on each boot. However, running the same - cloud-init-per command manually after boot doesn't reveal the issue. Turns - out the issue comes from 'migrator' cloud-init module which renames all - files in /var/lib/cloud/instance/sem/ replacing dashes with underscores. As - migrator runs before bootcmd it renames - - /var/lib/cloud/instance/sem/bootper.mycmd-bootcmd.instance - to - /var/lib/cloud/instance/sem/bootper.mycmd_bootcmd.instance - - so cloud-init-per doesn't see it and thinks that the comment was never ran - before. On next boot the sequence repeats. - - There are multiple ways to resolve the issue. This patch takes the - following approach: 'canonicalize' sem names by replacing dashes with - underscores (this is consistent with post-'migrator' contents of - /var/lib/cloud/instance/sem/). We, however, need to be careful: in case - someone had a command with dashes before and he had migrator module enables - we need to see the old sem file (or the command will run again and this can - be as bad as formatting a partition!) so we add a small 'migrator' part to - cloud-init-per script itself checking for legacy sem names. - - Signed-off-by: Vitaly Kuznetsov - -commit 9cf9d8cdd3a8fd7d4d425f7051122d0ac8af2bbd -Author: Vitaly Kuznetsov -Date: Mon Feb 18 22:55:49 2019 +0000 - - This is to fix https://bugs.launchpad.net/cloud-init/+bug/1812676 - -Resolves: rhbz#1664876 -X-downstream-only: false - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina -Signed-off-by: Danilo C. L. de Paula ---- - tools/cloud-init-per | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/tools/cloud-init-per b/tools/cloud-init-per -index 7d6754b6..eae3e93f 100755 ---- a/tools/cloud-init-per -+++ b/tools/cloud-init-per -@@ -38,7 +38,7 @@ fi - [ "$1" = "-h" -o "$1" = "--help" ] && { Usage ; exit 0; } - [ $# -ge 3 ] || { Usage 1>&2; exit 1; } - freq=$1 --name=$2 -+name=${2/-/_} - shift 2; - - [ "${name#*/}" = "${name}" ] || fail "name cannot contain a /" -@@ -53,6 +53,12 @@ esac - [ -d "${sem%/*}" ] || mkdir -p "${sem%/*}" || - fail "failed to make directory for ${sem}" - -+# Rename legacy sem files with dashes in their names. Do not overwrite existing -+# sem files to prevent clobbering those which may have been created from calls -+# outside of cloud-init. -+sem_legacy="${sem/_/-}" -+[ "$sem" != "$sem_legacy" -a -e "$sem_legacy" ] && mv -n "$sem_legacy" "$sem" -+ - [ "$freq" != "always" -a -e "$sem" ] && exit 0 - "$@" - ret=$? --- -2.20.1 - diff --git a/SOURCES/0011-azure-Filter-list-of-ssh-keys-pulled-from-fabric.patch b/SOURCES/0011-azure-Filter-list-of-ssh-keys-pulled-from-fabric.patch deleted file mode 100644 index bb63c25..0000000 --- a/SOURCES/0011-azure-Filter-list-of-ssh-keys-pulled-from-fabric.patch +++ /dev/null @@ -1,572 +0,0 @@ -From 8e168f17b0c138d589f7b3bea4a4b6fcc8e5e03f Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 6 Mar 2019 14:20:18 +0100 -Subject: azure: Filter list of ssh keys pulled from fabric - -RH-Author: Eduardo Otubo -Message-id: <20190306142018.8902-1-otubo@redhat.com> -Patchwork-id: 84807 -O-Subject: [RHEL-7.7 cloud-init PATCH] azure: Filter list of ssh keys pulled from fabric -Bugzilla: 1684040 -RH-Acked-by: Cathy Avery -RH-Acked-by: Vitaly Kuznetsov - -From: "Jason Zions (MSFT)" - -commit 34f54360fcc1e0f805002a0b639d0a84eb2cb8ee -Author: Jason Zions (MSFT) -Date: Fri Feb 22 13:26:31 2019 +0000 - - azure: Filter list of ssh keys pulled from fabric - - The Azure data source is expected to expose a list of - ssh keys for the user-to-be-provisioned in the crawled - metadata. When configured to use the __builtin__ agent - this list is built by the WALinuxAgentShim. The shim - retrieves the full set of certificates and public keys - exposed to the VM from the wireserver, extracts any - ssh keys it can, and returns that list. - - This fix reduces that list of ssh keys to just the - ones whose fingerprints appear in the "administrative - user" section of the ovf-env.xml file. The Azure - control plane exposes other ssh keys to the VM for - other reasons, but those should not be added to the - authorized_keys file for the provisioned user. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina -Signed-off-by: Danilo C. L. de Paula ---- - cloudinit/sources/DataSourceAzure.py | 13 +- - cloudinit/sources/helpers/azure.py | 109 +++++++++---- - .../azure/parse_certificates_fingerprints | 4 + - tests/data/azure/parse_certificates_pem | 152 ++++++++++++++++++ - tests/data/azure/pubkey_extract_cert | 13 ++ - tests/data/azure/pubkey_extract_ssh_key | 1 + - .../test_datasource/test_azure_helper.py | 71 +++++++- - 7 files changed, 322 insertions(+), 41 deletions(-) - create mode 100644 tests/data/azure/parse_certificates_fingerprints - create mode 100644 tests/data/azure/parse_certificates_pem - create mode 100644 tests/data/azure/pubkey_extract_cert - create mode 100644 tests/data/azure/pubkey_extract_ssh_key - -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -index 7dbeb04c..2062ca5d 100644 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -627,9 +627,11 @@ class DataSourceAzure(sources.DataSource): - if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN: - self.bounce_network_with_azure_hostname() - -+ pubkey_info = self.cfg.get('_pubkeys', None) - metadata_func = partial(get_metadata_from_fabric, - fallback_lease_file=self. -- dhclient_lease_file) -+ dhclient_lease_file, -+ pubkey_info=pubkey_info) - else: - metadata_func = self.get_metadata_from_agent - -@@ -642,6 +644,7 @@ class DataSourceAzure(sources.DataSource): - "Error communicating with Azure fabric; You may experience." - "connectivity issues.", exc_info=True) - return False -+ - util.del_file(REPORTED_READY_MARKER_FILE) - util.del_file(REPROVISION_MARKER_FILE) - return fabric_data -@@ -909,13 +912,15 @@ def find_child(node, filter_func): - def load_azure_ovf_pubkeys(sshnode): - # This parses a 'SSH' node formatted like below, and returns - # an array of dicts. -- # [{'fp': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7', -- # 'path': 'where/to/go'}] -+ # [{'fingerprint': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7', -+ # 'path': '/where/to/go'}] - # - # -- # ABC/ABC -+ # ABC/x/y/z - # ... - # -+ # Under some circumstances, there may be a element along with the -+ # Fingerprint and Path. Pass those along if they appear. - results = find_child(sshnode, lambda n: n.localName == "PublicKeys") - if len(results) == 0: - return [] -diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py -index e5696b1f..2829dd20 100644 ---- a/cloudinit/sources/helpers/azure.py -+++ b/cloudinit/sources/helpers/azure.py -@@ -138,9 +138,36 @@ class OpenSSLManager(object): - self.certificate = certificate - LOG.debug('New certificate generated.') - -- def parse_certificates(self, certificates_xml): -- tag = ElementTree.fromstring(certificates_xml).find( -- './/Data') -+ @staticmethod -+ def _run_x509_action(action, cert): -+ cmd = ['openssl', 'x509', '-noout', action] -+ result, _ = util.subp(cmd, data=cert) -+ return result -+ -+ def _get_ssh_key_from_cert(self, certificate): -+ pub_key = self._run_x509_action('-pubkey', certificate) -+ keygen_cmd = ['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin'] -+ ssh_key, _ = util.subp(keygen_cmd, data=pub_key) -+ return ssh_key -+ -+ def _get_fingerprint_from_cert(self, certificate): -+ """openssl x509 formats fingerprints as so: -+ 'SHA1 Fingerprint=07:3E:19:D1:4D:1C:79:92:24:C6:A0:FD:8D:DA:\ -+ B6:A8:BF:27:D4:73\n' -+ -+ Azure control plane passes that fingerprint as so: -+ '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473' -+ """ -+ raw_fp = self._run_x509_action('-fingerprint', certificate) -+ eq = raw_fp.find('=') -+ octets = raw_fp[eq+1:-1].split(':') -+ return ''.join(octets) -+ -+ def _decrypt_certs_from_xml(self, certificates_xml): -+ """Decrypt the certificates XML document using the our private key; -+ return the list of certs and private keys contained in the doc. -+ """ -+ tag = ElementTree.fromstring(certificates_xml).find('.//Data') - certificates_content = tag.text - lines = [ - b'MIME-Version: 1.0', -@@ -151,32 +178,30 @@ class OpenSSLManager(object): - certificates_content.encode('utf-8'), - ] - with cd(self.tmpdir): -- with open('Certificates.p7m', 'wb') as f: -- f.write(b'\n'.join(lines)) - out, _ = util.subp( -- 'openssl cms -decrypt -in Certificates.p7m -inkey' -+ 'openssl cms -decrypt -in /dev/stdin -inkey' - ' {private_key} -recip {certificate} | openssl pkcs12 -nodes' - ' -password pass:'.format(**self.certificate_names), -- shell=True) -- private_keys, certificates = [], [] -+ shell=True, data=b'\n'.join(lines)) -+ return out -+ -+ def parse_certificates(self, certificates_xml): -+ """Given the Certificates XML document, return a dictionary of -+ fingerprints and associated SSH keys derived from the certs.""" -+ out = self._decrypt_certs_from_xml(certificates_xml) - current = [] -+ keys = {} - for line in out.splitlines(): - current.append(line) - if re.match(r'[-]+END .*?KEY[-]+$', line): -- private_keys.append('\n'.join(current)) -+ # ignore private_keys - current = [] - elif re.match(r'[-]+END .*?CERTIFICATE[-]+$', line): -- certificates.append('\n'.join(current)) -+ certificate = '\n'.join(current) -+ ssh_key = self._get_ssh_key_from_cert(certificate) -+ fingerprint = self._get_fingerprint_from_cert(certificate) -+ keys[fingerprint] = ssh_key - current = [] -- keys = [] -- for certificate in certificates: -- with cd(self.tmpdir): -- public_key, _ = util.subp( -- 'openssl x509 -noout -pubkey |' -- 'ssh-keygen -i -m PKCS8 -f /dev/stdin', -- data=certificate, -- shell=True) -- keys.append(public_key) - return keys - - -@@ -206,7 +231,6 @@ class WALinuxAgentShim(object): - self.dhcpoptions = dhcp_options - self._endpoint = None - self.openssl_manager = None -- self.values = {} - self.lease_file = fallback_lease_file - - def clean_up(self): -@@ -328,8 +352,9 @@ class WALinuxAgentShim(object): - LOG.debug('Azure endpoint found at %s', endpoint_ip_address) - return endpoint_ip_address - -- def register_with_azure_and_fetch_data(self): -- self.openssl_manager = OpenSSLManager() -+ def register_with_azure_and_fetch_data(self, pubkey_info=None): -+ if self.openssl_manager is None: -+ self.openssl_manager = OpenSSLManager() - http_client = AzureEndpointHttpClient(self.openssl_manager.certificate) - LOG.info('Registering with Azure...') - attempts = 0 -@@ -347,16 +372,37 @@ class WALinuxAgentShim(object): - attempts += 1 - LOG.debug('Successfully fetched GoalState XML.') - goal_state = GoalState(response.contents, http_client) -- public_keys = [] -- if goal_state.certificates_xml is not None: -+ ssh_keys = [] -+ if goal_state.certificates_xml is not None and pubkey_info is not None: - LOG.debug('Certificate XML found; parsing out public keys.') -- public_keys = self.openssl_manager.parse_certificates( -+ keys_by_fingerprint = self.openssl_manager.parse_certificates( - goal_state.certificates_xml) -- data = { -- 'public-keys': public_keys, -- } -+ ssh_keys = self._filter_pubkeys(keys_by_fingerprint, pubkey_info) - self._report_ready(goal_state, http_client) -- return data -+ return {'public-keys': ssh_keys} -+ -+ def _filter_pubkeys(self, keys_by_fingerprint, pubkey_info): -+ """cloud-init expects a straightforward array of keys to be dropped -+ into the user's authorized_keys file. Azure control plane exposes -+ multiple public keys to the VM via wireserver. Select just the -+ user's key(s) and return them, ignoring any other certs. -+ """ -+ keys = [] -+ for pubkey in pubkey_info: -+ if 'value' in pubkey and pubkey['value']: -+ keys.append(pubkey['value']) -+ elif 'fingerprint' in pubkey and pubkey['fingerprint']: -+ fingerprint = pubkey['fingerprint'] -+ if fingerprint in keys_by_fingerprint: -+ keys.append(keys_by_fingerprint[fingerprint]) -+ else: -+ LOG.warning("ovf-env.xml specified PublicKey fingerprint " -+ "%s not found in goalstate XML", fingerprint) -+ else: -+ LOG.warning("ovf-env.xml specified PublicKey with neither " -+ "value nor fingerprint: %s", pubkey) -+ -+ return keys - - def _report_ready(self, goal_state, http_client): - LOG.debug('Reporting ready to Azure fabric.') -@@ -373,11 +419,12 @@ class WALinuxAgentShim(object): - LOG.info('Reported ready to Azure fabric.') - - --def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None): -+def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None, -+ pubkey_info=None): - shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file, - dhcp_options=dhcp_opts) - try: -- return shim.register_with_azure_and_fetch_data() -+ return shim.register_with_azure_and_fetch_data(pubkey_info=pubkey_info) - finally: - shim.clean_up() - -diff --git a/tests/data/azure/parse_certificates_fingerprints b/tests/data/azure/parse_certificates_fingerprints -new file mode 100644 -index 00000000..f7293c56 ---- /dev/null -+++ b/tests/data/azure/parse_certificates_fingerprints -@@ -0,0 +1,4 @@ -+ECEDEB3B8488D31AF3BC4CCED493F64B7D27D7B1 -+073E19D14D1C799224C6A0FD8DDAB6A8BF27D473 -+4C16E7FAD6297D74A9B25EB8F0A12808CEBE293E -+929130695289B450FE45DCD5F6EF0CDE69865867 -diff --git a/tests/data/azure/parse_certificates_pem b/tests/data/azure/parse_certificates_pem -new file mode 100644 -index 00000000..3521ea3a ---- /dev/null -+++ b/tests/data/azure/parse_certificates_pem -@@ -0,0 +1,152 @@ -+Bag Attributes -+ localKeyID: 01 00 00 00 -+ Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0 -+Key Attributes -+ X509v3 Key Usage: 10 -+-----BEGIN PRIVATE KEY----- -+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDlEe5fUqwdrQTP -+W2oVlGK2f31q/8ULT8KmOTyUvL0RPdJQ69vvHOc5Q2CKg2eviHC2LWhF8WmpnZj6 -+61RL0GeFGizwvU8Moebw5p3oqdcgoGpHVtxf+mr4QcWF58/Fwez0dA4hcsimVNBz -+eNpBBUIKNBMTBG+4d6hcQBUAGKUdGRcCGEyTqXLU0MgHjxC9JgVqWJl+X2LcAGj5 -+7J+tGYGTLzKJmeCeGVNN5ZtJ0T85MYHCKQk1/FElK+Kq5akovXffQHjlnCPcx0NJ -+47NBjlPaFp2gjnAChn79bT4iCjOFZ9avWpqRpeU517UCnY7djOr3fuod/MSQyh3L -+Wuem1tWBAgMBAAECggEBAM4ZXQRs6Kjmo95BHGiAEnSqrlgX+dycjcBq3QPh8KZT -+nifqnf48XhnackENy7tWIjr3DctoUq4mOp8AHt77ijhqfaa4XSg7fwKeK9NLBGC5 -+lAXNtAey0o2894/sKrd+LMkgphoYIUnuI4LRaGV56potkj/ZDP/GwTcG/R4SDnTn -+C1Nb05PNTAPQtPZrgPo7TdM6gGsTnFbVrYHQLyg2Sq/osHfF15YohB01esRLCAwb -+EF8JkRC4hWIZoV7BsyQ39232zAJQGGla7+wKFs3kObwh3VnFkQpT94KZnNiZuEfG -+x5pW4Pn3gXgNsftscXsaNe/M9mYZqo//Qw7NvUIvAvECgYEA9AVveyK0HOA06fhh -++3hUWdvw7Pbrl+e06jO9+bT1RjQMbHKyI60DZyVGuAySN86iChJRoJr5c6xj+iXU -+cR6BVJDjGH5t1tyiK2aYf6hEpK9/j8Z54UiVQ486zPP0PGfT2TO4lBLK+8AUmoaH -+gk21ul8QeVCeCJa/o+xEoRFvzcUCgYEA8FCbbvInrUtNY+9eKaUYoNodsgBVjm5X -+I0YPUL9D4d+1nvupHSV2NVmQl0w1RaJwrNTafrl5LkqjhQbmuWNta6QgfZzSA3LB -+lWXo1Mm0azKdcD3qMGbvn0Q3zU+yGNEgmB/Yju3/NtgYRG6tc+FCWRbPbiCnZWT8 -+v3C2Y0XggI0CgYEA2/jCZBgGkTkzue5kNVJlh5OS/aog+pCvL6hxCtarfBuTT3ed -+Sje+p46cz3DVpmUpATc+Si8py7KNdYQAm/BJ2be6X+woi9Xcgo87zWgcaPCjZzId -+0I2jsIE/Gl6XvpRCDrxnGWRPgt3GNP4szbPLrDPiH9oie8+Y9eYYf7G+PZkCgYEA -+nRSzZOPYV4f/QDF4pVQLMykfe/iH9B/fyWjEHg3He19VQmRReIHCMMEoqBziPXAe -+onpHj8oAkeer1wpZyhhZr6CKtFDLXgGm09bXSC/IRMHC81klORovyzU2HHfZfCtG -+WOmIDnU2+0xpIGIP8sztJ3qnf97MTJSkOSadsWo9gwkCgYEAh5AQmJQmck88Dff2 -+qIfJIX8d+BDw47BFJ89OmMFjGV8TNB+JO+AV4Vkodg4hxKpLqTFZTTUFgoYfy5u1 -+1/BhAjpmCDCrzubCFhx+8VEoM2+2+MmnuQoMAm9+/mD/IidwRaARgXgvEmp7sfdt -+RyWd+p2lYvFkC/jORQtDMY4uW1o= -+-----END PRIVATE KEY----- -+Bag Attributes -+ localKeyID: 02 00 00 00 -+ Microsoft CSP Name: Microsoft Strong Cryptographic Provider -+Key Attributes -+ X509v3 Key Usage: 10 -+-----BEGIN PRIVATE KEY----- -+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlQhPrZwVQYFV4 -+FBc0H1iTXYaznMpwZvEITKtXWACzTdguUderEVOkXW3HTi5HvC2rMayt0nqo3zcd -+x1eGiqdjpZQ/wMrkz9wNEM/nNMsXntEwxk0jCVNKB/jz6vf+BOtrSI01SritAGZW -+dpKoTUyztT8C2mA3X6D8g3m4Dd07ltnzxaDqAQIU5jBHh3f/Q14tlPNZWUIiqVTC -+gDxgAe7MDmfs9h3CInTBX1XM5J4UsLTL23/padgeSvP5YF5qr1+0c7Tdftxr2lwA -+N3rLkisf5EiLAToVyJJlgP/exo2I8DaIKe7DZzD3Y1CrurOpkcMKYu5kM1Htlbua -+tDkAa2oDAgMBAAECggEAOvdueS9DyiMlCKAeQb1IQosdQOh0l0ma+FgEABC2CWhd -+0LgjQTBRM6cGO+urcq7/jhdWQ1UuUG4tVn71z7itCi/F/Enhxc2C22d2GhFVpWsn -+giSXJYpZ/mIjkdVfWNo6FRuRmmHwMys1p0qTOS+8qUJWhSzW75csqJZGgeUrAI61 -+LBV5F0SGR7dR2xZfy7PeDs9xpD0QivDt5DpsZWPaPvw4QlhdLgw6/YU1h9vtm6ci -+xLjnPRLZ7JMpcQHO8dUDl6FiEI7yQ11BDm253VQAVMddYRPQABn7SpEF8kD/aZVh -+2Clvz61Rz80SKjPUthMPLWMCRp7zB0xDMzt3/1i+tQKBgQD6Ar1/oD3eFnRnpi4u -+n/hdHJtMuXWNfUA4dspNjP6WGOid9sgIeUUdif1XyVJ+afITzvgpWc7nUWIqG2bQ -+WxJ/4q2rjUdvjNXTy1voVungR2jD5WLQ9DKeaTR0yCliWlx4JgdPG7qGI5MMwsr+ -+R/PUoUUhGeEX+o/sCSieO3iUrQKBgQDqwBEMvIdhAv/CK2sG3fsKYX8rFT55ZNX3 -+Tix9DbUGY3wQColNuI8U1nDlxE9U6VOfT9RPqKelBLCgbzB23kdEJnjSlnqlTxrx -+E+Hkndyf2ckdJAR3XNxoQ6SRLJNBsgoBj/z5tlfZE9/Jc+uh0mYy3e6g6XCVPBcz -+MgoIc+ofbwKBgQCGQhZ1hR30N+bHCozeaPW9OvGDIE0qcEqeh9xYDRFilXnF6pK9 -+SjJ9jG7KR8jPLiHb1VebDSl5O1EV/6UU2vNyTc6pw7LLCryBgkGW4aWy1WZDXNnW -+EG1meGS9GghvUss5kmJ2bxOZmV0Mi0brisQ8OWagQf+JGvtS7BAt+Q3l+QKBgAb9 -+8YQPmXiqPjPqVyW9Ntz4SnFeEJ5NApJ7IZgX8GxgSjGwHqbR+HEGchZl4ncE/Bii -+qBA3Vcb0fM5KgYcI19aPzsl28fA6ivLjRLcqfIfGVNcpW3iyq13vpdctHLW4N9QU -+FdTaOYOds+ysJziKq8CYG6NvUIshXw+HTgUybqbBAoGBAIIOqcmmtgOClAwipA17 -+dAHsI9Sjk+J0+d4JU6o+5TsmhUfUKIjXf5+xqJkJcQZMEe5GhxcCuYkgFicvh4Hz -+kv2H/EU35LcJTqC6KTKZOWIbGcn1cqsvwm3GQJffYDiO8fRZSwCaif2J3F2lfH4Y -+R/fA67HXFSTT+OncdRpY1NOn -+-----END PRIVATE KEY----- -+Bag Attributes: -+subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US -+issuer=/CN=Root Agency -+-----BEGIN CERTIFICATE----- -+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290 -+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV -+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv -+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE -+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIlPjJXzrRih4C -+k/XsoI01oqo7IUxH3dA2F7vHGXQoIpKCp8Qe6Z6cFfdD8Uj+s+B1BX6hngwzIwjN -+jE/23X3SALVzJVWzX4Y/IEjbgsuao6sOyNyB18wIU9YzZkVGj68fmMlUw3LnhPbe -+eWkufZaJCaLyhQOwlRMbOcn48D6Ys8fccOyXNzpq3rH1OzeQpxS2M8zaJYP4/VZ/ -+sf6KRpI7bP+QwyFvNKfhcaO9/gj4kMo9lVGjvDU20FW6g8UVNJCV9N4GO6mOcyqo -+OhuhVfjCNGgW7N1qi0TIVn0/MQM4l4dcT2R7Z/bV9fhMJLjGsy5A4TLAdRrhKUHT -+bzi9HyDvAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA= -+-----END CERTIFICATE----- -+Bag Attributes -+ localKeyID: 01 00 00 00 -+subject=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com -+issuer=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com -+-----BEGIN CERTIFICATE----- -+MIID7TCCAtWgAwIBAgIJALQS3yMg3R41MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD -+VQQGEwJVUzETMBEGA1UECAwKV0FTSElOR1RPTjEQMA4GA1UEBwwHU2VhdHRsZTES -+MBAGA1UECgwJTWljcm9zb2Z0MQ4wDAYDVQQLDAVBenVyZTEOMAwGA1UEAwwFQW5o -+Vm8xIjAgBgkqhkiG9w0BCQEWE2FuaHZvQG1pY3Jvc29mdC5jb20wHhcNMTkwMjE0 -+MjMxMjQwWhcNMjExMTEwMjMxMjQwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgM -+CldBU0hJTkdUT04xEDAOBgNVBAcMB1NlYXR0bGUxEjAQBgNVBAoMCU1pY3Jvc29m -+dDEOMAwGA1UECwwFQXp1cmUxDjAMBgNVBAMMBUFuaFZvMSIwIAYJKoZIhvcNAQkB -+FhNhbmh2b0BtaWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -+CgKCAQEA5RHuX1KsHa0Ez1tqFZRitn99av/FC0/Cpjk8lLy9ET3SUOvb7xznOUNg -+ioNnr4hwti1oRfFpqZ2Y+utUS9BnhRos8L1PDKHm8Oad6KnXIKBqR1bcX/pq+EHF -+hefPxcHs9HQOIXLIplTQc3jaQQVCCjQTEwRvuHeoXEAVABilHRkXAhhMk6ly1NDI -+B48QvSYFaliZfl9i3ABo+eyfrRmBky8yiZngnhlTTeWbSdE/OTGBwikJNfxRJSvi -+quWpKL1330B45Zwj3MdDSeOzQY5T2hadoI5wAoZ+/W0+IgozhWfWr1qakaXlOde1 -+Ap2O3Yzq937qHfzEkMody1rnptbVgQIDAQABo1AwTjAdBgNVHQ4EFgQUPvdgLiv3 -+pAk4r0QTPZU3PFOZJvgwHwYDVR0jBBgwFoAUPvdgLiv3pAk4r0QTPZU3PFOZJvgw -+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVUHZT+h9+uCPLTEl5IDg -+kqd9WpzXA7PJd/V+7DeDDTkEd06FIKTWZLfxLVVDjQJnQqubQb//e0zGu1qKbXnX -+R7xqWabGU4eyPeUFWddmt1OHhxKLU3HbJNJJdL6XKiQtpGGUQt/mqNQ/DEr6hhNF -+im5I79iA8H/dXA2gyZrj5Rxea4mtsaYO0mfp1NrFtJpAh2Djy4B1lBXBIv4DWG9e -+mMEwzcLCOZj2cOMA6+mdLMUjYCvIRtnn5MKUHyZX5EmX79wsqMTvVpddlVLB9Kgz -+Qnvft9+SBWh9+F3ip7BsL6Q4Q9v8eHRbnP0ya7ddlgh64uwf9VOfZZdKCnwqudJP -+3g== -+-----END CERTIFICATE----- -+Bag Attributes -+ localKeyID: 02 00 00 00 -+subject=/CN=/subscriptions/redacted/resourcegroups/redacted/providers/Microsoft.Compute/virtualMachines/redacted -+issuer=/CN=Microsoft.ManagedIdentity -+-----BEGIN CERTIFICATE----- -+MIIDnTCCAoWgAwIBAgIUB2lauSRccvFkoJybUfIwOUqBN7MwDQYJKoZIhvcNAQEL -+BQAwJDEiMCAGA1UEAxMZTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eTAeFw0xOTAy -+MTUxOTA5MDBaFw0xOTA4MTQxOTA5MDBaMIGUMYGRMIGOBgNVBAMTgYYvc3Vic2Ny -+aXB0aW9ucy8yN2I3NTBjZC1lZDQzLTQyZmQtOTA0NC04ZDc1ZTEyNGFlNTUvcmVz -+b3VyY2Vncm91cHMvYW5oZXh0cmFzc2gvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21w -+dXRlL3ZpcnR1YWxNYWNoaW5lcy9hbmh0ZXN0Y2VydDCCASIwDQYJKoZIhvcNAQEB -+BQADggEPADCCAQoCggEBAOVCE+tnBVBgVXgUFzQfWJNdhrOcynBm8QhMq1dYALNN -+2C5R16sRU6RdbcdOLke8LasxrK3SeqjfNx3HV4aKp2OllD/AyuTP3A0Qz+c0yxee -+0TDGTSMJU0oH+PPq9/4E62tIjTVKuK0AZlZ2kqhNTLO1PwLaYDdfoPyDebgN3TuW -+2fPFoOoBAhTmMEeHd/9DXi2U81lZQiKpVMKAPGAB7swOZ+z2HcIidMFfVczknhSw -+tMvbf+lp2B5K8/lgXmqvX7RztN1+3GvaXAA3esuSKx/kSIsBOhXIkmWA/97GjYjw -+Nogp7sNnMPdjUKu6s6mRwwpi7mQzUe2Vu5q0OQBragMCAwEAAaNWMFQwDgYDVR0P -+AQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYD -+VR0jBBgwFoAUOJvzEsriQWdJBndPrK+Me1bCPjYwDQYJKoZIhvcNAQELBQADggEB -+AFGP/g8o7Hv/to11M0UqfzJuW/AyH9RZtSRcNQFLZUndwweQ6fap8lFsA4REUdqe -+7Quqp5JNNY1XzKLWXMPoheIDH1A8FFXdsAroArzlNs9tO3TlIHE8A7HxEVZEmR4b -+7ZiixmkQPS2RkjEoV/GM6fheBrzuFn7X5kVZyE6cC5sfcebn8xhk3ZcXI0VmpdT0 -+jFBsf5IvFCIXXLLhJI4KXc8VMoKFU1jT9na/jyaoGmfwovKj4ib8s2aiXGAp7Y38 -+UCmY+bJapWom6Piy5Jzi/p/kzMVdJcSa+GqpuFxBoQYEVs2XYVl7cGu/wPM+NToC -+pkSoWwF1QAnHn0eokR9E1rU= -+-----END CERTIFICATE----- -+Bag Attributes: -+subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US -+issuer=/CN=Root Agency -+-----BEGIN CERTIFICATE----- -+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290 -+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV -+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv -+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE -+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb -+Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi -+nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW -+vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+ -+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y -+WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7 -+t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA= -+-----END CERTIFICATE----- -diff --git a/tests/data/azure/pubkey_extract_cert b/tests/data/azure/pubkey_extract_cert -new file mode 100644 -index 00000000..ce9b852d ---- /dev/null -+++ b/tests/data/azure/pubkey_extract_cert -@@ -0,0 +1,13 @@ -+-----BEGIN CERTIFICATE----- -+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290 -+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV -+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv -+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE -+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb -+Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi -+nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW -+vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+ -+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y -+WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7 -+t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA= -+-----END CERTIFICATE----- -diff --git a/tests/data/azure/pubkey_extract_ssh_key b/tests/data/azure/pubkey_extract_ssh_key -new file mode 100644 -index 00000000..54d749ed ---- /dev/null -+++ b/tests/data/azure/pubkey_extract_ssh_key -@@ -0,0 +1 @@ -+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHU9IDclbKVYVbYuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoinlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmWvwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4yWzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7t5btUyvp -diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py -index 26b2b93d..02556165 100644 ---- a/tests/unittests/test_datasource/test_azure_helper.py -+++ b/tests/unittests/test_datasource/test_azure_helper.py -@@ -1,11 +1,13 @@ - # This file is part of cloud-init. See LICENSE file for license information. - - import os -+import unittest2 - from textwrap import dedent - - from cloudinit.sources.helpers import azure as azure_helper - from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir - -+from cloudinit.util import load_file - from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim - - GOAL_STATE_TEMPLATE = """\ -@@ -289,6 +291,50 @@ class TestOpenSSLManager(CiTestCase): - self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list) - - -+class TestOpenSSLManagerActions(CiTestCase): -+ -+ def setUp(self): -+ super(TestOpenSSLManagerActions, self).setUp() -+ -+ self.allowed_subp = True -+ -+ def _data_file(self, name): -+ path = 'tests/data/azure' -+ return os.path.join(path, name) -+ -+ @unittest2.skip("todo move to cloud_test") -+ def test_pubkey_extract(self): -+ cert = load_file(self._data_file('pubkey_extract_cert')) -+ good_key = load_file(self._data_file('pubkey_extract_ssh_key')) -+ sslmgr = azure_helper.OpenSSLManager() -+ key = sslmgr._get_ssh_key_from_cert(cert) -+ self.assertEqual(good_key, key) -+ -+ good_fingerprint = '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473' -+ fingerprint = sslmgr._get_fingerprint_from_cert(cert) -+ self.assertEqual(good_fingerprint, fingerprint) -+ -+ @unittest2.skip("todo move to cloud_test") -+ @mock.patch.object(azure_helper.OpenSSLManager, '_decrypt_certs_from_xml') -+ def test_parse_certificates(self, mock_decrypt_certs): -+ """Azure control plane puts private keys as well as certificates -+ into the Certificates XML object. Make sure only the public keys -+ from certs are extracted and that fingerprints are converted to -+ the form specified in the ovf-env.xml file. -+ """ -+ cert_contents = load_file(self._data_file('parse_certificates_pem')) -+ fingerprints = load_file(self._data_file( -+ 'parse_certificates_fingerprints') -+ ).splitlines() -+ mock_decrypt_certs.return_value = cert_contents -+ sslmgr = azure_helper.OpenSSLManager() -+ keys_by_fp = sslmgr.parse_certificates('') -+ for fp in keys_by_fp.keys(): -+ self.assertIn(fp, fingerprints) -+ for fp in fingerprints: -+ self.assertIn(fp, keys_by_fp) -+ -+ - class TestWALinuxAgentShim(CiTestCase): - - def setUp(self): -@@ -329,18 +375,31 @@ class TestWALinuxAgentShim(CiTestCase): - - def test_certificates_used_to_determine_public_keys(self): - shim = wa_shim() -- data = shim.register_with_azure_and_fetch_data() -+ """if register_with_azure_and_fetch_data() isn't passed some info about -+ the user's public keys, there's no point in even trying to parse -+ the certificates -+ """ -+ mypk = [{'fingerprint': 'fp1', 'path': 'path1'}, -+ {'fingerprint': 'fp3', 'path': 'path3', 'value': ''}] -+ certs = {'fp1': 'expected-key', -+ 'fp2': 'should-not-be-found', -+ 'fp3': 'expected-no-value-key', -+ } -+ sslmgr = self.OpenSSLManager.return_value -+ sslmgr.parse_certificates.return_value = certs -+ data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk) - self.assertEqual( - [mock.call(self.GoalState.return_value.certificates_xml)], -- self.OpenSSLManager.return_value.parse_certificates.call_args_list) -- self.assertEqual( -- self.OpenSSLManager.return_value.parse_certificates.return_value, -- data['public-keys']) -+ sslmgr.parse_certificates.call_args_list) -+ self.assertIn('expected-key', data['public-keys']) -+ self.assertIn('expected-no-value-key', data['public-keys']) -+ self.assertNotIn('should-not-be-found', data['public-keys']) - - def test_absent_certificates_produces_empty_public_keys(self): -+ mypk = [{'fingerprint': 'fp1', 'path': 'path1'}] - self.GoalState.return_value.certificates_xml = None - shim = wa_shim() -- data = shim.register_with_azure_and_fetch_data() -+ data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk) - self.assertEqual([], data['public-keys']) - - def test_correct_url_used_for_report_ready(self): --- -2.20.1 - diff --git a/SOURCES/0012-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch b/SOURCES/0012-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch deleted file mode 100644 index 2010bad..0000000 --- a/SOURCES/0012-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch +++ /dev/null @@ -1,66 +0,0 @@ -From ffabcbbf0d4e990f04ab755dd87bb24e70c4fe78 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 20 Mar 2019 11:45:59 +0100 -Subject: include 'NOZEROCONF=yes' in /etc/sysconfig/network - -RH-Author: Eduardo Otubo -Message-id: <20190320114559.23708-1-otubo@redhat.com> -Patchwork-id: 84937 -O-Subject: [RHEL-7.7 cloud-init PATCH] include 'NOZEROCONF=yes' in /etc/sysconfig/network -Bugzilla: 1653131 -RH-Acked-by: Cathy Avery -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Vitaly Kuznetsov - -The option NOZEROCONF=yes is not included by default in -/etc/sysconfig/network, which is required by Overcloud instances. The -patch also includes tests for the modifications. - -X-downstream-only: yes -Resolves: rhbz#1653131 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina -Signed-off-by: Danilo C. L. de Paula ---- - cloudinit/net/sysconfig.py | 11 ++++++++++- - tests/unittests/test_net.py | 1 - - 2 files changed, 10 insertions(+), 2 deletions(-) - -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index dc1815d9..52bb8483 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -684,7 +684,16 @@ class Renderer(renderer.Renderer): - # Distros configuring /etc/sysconfig/network as a file e.g. Centos - if sysconfig_path.endswith('network'): - util.ensure_dir(os.path.dirname(sysconfig_path)) -- netcfg = [_make_header(), 'NETWORKING=yes'] -+ netcfg = [] -+ for line in util.load_file(sysconfig_path, quiet=True).split('\n'): -+ if 'cloud-init' in line: -+ break -+ if not line.startswith(('NETWORKING=', -+ 'IPV6_AUTOCONF=', -+ 'NETWORKING_IPV6=')): -+ netcfg.append(line) -+ # Now generate the cloud-init portion of sysconfig/network -+ netcfg.extend([_make_header(), 'NETWORKING=yes']) - if network_state.use_ipv6: - netcfg.append('NETWORKING_IPV6=yes') - netcfg.append('IPV6_AUTOCONF=no') -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 526a30ed..012c43b5 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -887,7 +887,6 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - BOOTPROTO=none - DEVICE=bond0 - DHCPV6C=yes -- IPV6_AUTOCONF=no - IPV6INIT=yes - MACADDR=aa:bb:cc:dd:ee:ff - ONBOOT=yes --- -2.20.1 - diff --git a/SOURCES/ci-Azure-Changes-to-the-Hyper-V-KVP-Reporter.patch b/SOURCES/ci-Azure-Changes-to-the-Hyper-V-KVP-Reporter.patch deleted file mode 100644 index 7fe1d64..0000000 --- a/SOURCES/ci-Azure-Changes-to-the-Hyper-V-KVP-Reporter.patch +++ /dev/null @@ -1,405 +0,0 @@ -From f919e65e4a462b385a6daa6b7cccc6af1358cbcf Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 29 May 2019 13:41:47 +0200 -Subject: [PATCH 3/5] Azure: Changes to the Hyper-V KVP Reporter -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -RH-Author: Eduardo Otubo -Message-id: <20190529134149.842-4-otubo@redhat.com> -Patchwork-id: 88266 -O-Subject: [RHEL-8.0.1/RHEL-8.1.0 cloud-init PATCHv2 3/5] Azure: Changes to the Hyper-V KVP Reporter -Bugzilla: 1691986 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Cathy Avery - -From: Anh Vo -commit 86674f013dfcea3c075ab41373ffb475881066f6 -Author: Anh Vo -Date: Mon Apr 29 20:22:16 2019 +0000 - - Azure: Changes to the Hyper-V KVP Reporter - -  + Truncate KVP Pool file to prevent stale entries from - being processed by the Hyper-V KVP reporter. -  + Drop filtering of KVPs as it is no longer needed. -  + Batch appending of existing KVP entries. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/reporting/handlers.py | 117 +++++++++++++++---------------- - tests/unittests/test_reporting_hyperv.py | 104 +++++++++++++-------------- - 2 files changed, 106 insertions(+), 115 deletions(-) - mode change 100644 => 100755 cloudinit/reporting/handlers.py - mode change 100644 => 100755 tests/unittests/test_reporting_hyperv.py - -diff --git a/cloudinit/reporting/handlers.py b/cloudinit/reporting/handlers.py -old mode 100644 -new mode 100755 -index 6d23558..10165ae ---- a/cloudinit/reporting/handlers.py -+++ b/cloudinit/reporting/handlers.py -@@ -5,7 +5,6 @@ import fcntl - import json - import six - import os --import re - import struct - import threading - import time -@@ -14,6 +13,7 @@ from cloudinit import log as logging - from cloudinit.registry import DictRegistry - from cloudinit import (url_helper, util) - from datetime import datetime -+from six.moves.queue import Empty as QueueEmptyError - - if six.PY2: - from multiprocessing.queues import JoinableQueue as JQueue -@@ -129,24 +129,50 @@ class HyperVKvpReportingHandler(ReportingHandler): - DESC_IDX_KEY = 'msg_i' - JSON_SEPARATORS = (',', ':') - KVP_POOL_FILE_GUEST = '/var/lib/hyperv/.kvp_pool_1' -+ _already_truncated_pool_file = False - - def __init__(self, - kvp_file_path=KVP_POOL_FILE_GUEST, - event_types=None): - super(HyperVKvpReportingHandler, self).__init__() - self._kvp_file_path = kvp_file_path -+ HyperVKvpReportingHandler._truncate_guest_pool_file( -+ self._kvp_file_path) -+ - self._event_types = event_types - self.q = JQueue() -- self.kvp_file = None - self.incarnation_no = self._get_incarnation_no() - self.event_key_prefix = u"{0}|{1}".format(self.EVENT_PREFIX, - self.incarnation_no) -- self._current_offset = 0 - self.publish_thread = threading.Thread( - target=self._publish_event_routine) - self.publish_thread.daemon = True - self.publish_thread.start() - -+ @classmethod -+ def _truncate_guest_pool_file(cls, kvp_file): -+ """ -+ Truncate the pool file if it has not been truncated since boot. -+ This should be done exactly once for the file indicated by -+ KVP_POOL_FILE_GUEST constant above. This method takes a filename -+ so that we can use an arbitrary file during unit testing. -+ Since KVP is a best-effort telemetry channel we only attempt to -+ truncate the file once and only if the file has not been modified -+ since boot. Additional truncation can lead to loss of existing -+ KVPs. -+ """ -+ if cls._already_truncated_pool_file: -+ return -+ boot_time = time.time() - float(util.uptime()) -+ try: -+ if os.path.getmtime(kvp_file) < boot_time: -+ with open(kvp_file, "w"): -+ pass -+ except (OSError, IOError) as e: -+ LOG.warning("failed to truncate kvp pool file, %s", e) -+ finally: -+ cls._already_truncated_pool_file = True -+ - def _get_incarnation_no(self): - """ - use the time passed as the incarnation number. -@@ -162,20 +188,15 @@ class HyperVKvpReportingHandler(ReportingHandler): - - def _iterate_kvps(self, offset): - """iterate the kvp file from the current offset.""" -- try: -- with open(self._kvp_file_path, 'rb+') as f: -- self.kvp_file = f -- fcntl.flock(f, fcntl.LOCK_EX) -- f.seek(offset) -+ with open(self._kvp_file_path, 'rb') as f: -+ fcntl.flock(f, fcntl.LOCK_EX) -+ f.seek(offset) -+ record_data = f.read(self.HV_KVP_RECORD_SIZE) -+ while len(record_data) == self.HV_KVP_RECORD_SIZE: -+ kvp_item = self._decode_kvp_item(record_data) -+ yield kvp_item - record_data = f.read(self.HV_KVP_RECORD_SIZE) -- while len(record_data) == self.HV_KVP_RECORD_SIZE: -- self._current_offset += self.HV_KVP_RECORD_SIZE -- kvp_item = self._decode_kvp_item(record_data) -- yield kvp_item -- record_data = f.read(self.HV_KVP_RECORD_SIZE) -- fcntl.flock(f, fcntl.LOCK_UN) -- finally: -- self.kvp_file = None -+ fcntl.flock(f, fcntl.LOCK_UN) - - def _event_key(self, event): - """ -@@ -207,23 +228,13 @@ class HyperVKvpReportingHandler(ReportingHandler): - - return {'key': k, 'value': v} - -- def _update_kvp_item(self, record_data): -- if self.kvp_file is None: -- raise ReportException( -- "kvp file '{0}' not opened." -- .format(self._kvp_file_path)) -- self.kvp_file.seek(-self.HV_KVP_RECORD_SIZE, 1) -- self.kvp_file.write(record_data) -- - def _append_kvp_item(self, record_data): -- with open(self._kvp_file_path, 'rb+') as f: -+ with open(self._kvp_file_path, 'ab') as f: - fcntl.flock(f, fcntl.LOCK_EX) -- # seek to end of the file -- f.seek(0, 2) -- f.write(record_data) -+ for data in record_data: -+ f.write(data) - f.flush() - fcntl.flock(f, fcntl.LOCK_UN) -- self._current_offset = f.tell() - - def _break_down(self, key, meta_data, description): - del meta_data[self.MSG_KEY] -@@ -279,40 +290,26 @@ class HyperVKvpReportingHandler(ReportingHandler): - - def _publish_event_routine(self): - while True: -+ items_from_queue = 0 - try: - event = self.q.get(block=True) -- need_append = True -+ items_from_queue += 1 -+ encoded_data = [] -+ while event is not None: -+ encoded_data += self._encode_event(event) -+ try: -+ # get all the rest of the events in the queue -+ event = self.q.get(block=False) -+ items_from_queue += 1 -+ except QueueEmptyError: -+ event = None - try: -- if not os.path.exists(self._kvp_file_path): -- LOG.warning( -- "skip writing events %s to %s. file not present.", -- event.as_string(), -- self._kvp_file_path) -- encoded_event = self._encode_event(event) -- # for each encoded_event -- for encoded_data in (encoded_event): -- for kvp in self._iterate_kvps(self._current_offset): -- match = ( -- re.match( -- r"^{0}\|(\d+)\|.+" -- .format(self.EVENT_PREFIX), -- kvp['key'] -- )) -- if match: -- match_groups = match.groups(0) -- if int(match_groups[0]) < self.incarnation_no: -- need_append = False -- self._update_kvp_item(encoded_data) -- continue -- if need_append: -- self._append_kvp_item(encoded_data) -- except IOError as e: -- LOG.warning( -- "failed posting event to kvp: %s e:%s", -- event.as_string(), e) -+ self._append_kvp_item(encoded_data) -+ except (OSError, IOError) as e: -+ LOG.warning("failed posting events to kvp, %s", e) - finally: -- self.q.task_done() -- -+ for _ in range(items_from_queue): -+ self.q.task_done() - # when main process exits, q.get() will through EOFError - # indicating we should exit this thread. - except EOFError: -@@ -322,7 +319,7 @@ class HyperVKvpReportingHandler(ReportingHandler): - # if the kvp pool already contains a chunk of data, - # so defer it to another thread. - def publish_event(self, event): -- if (not self._event_types or event.event_type in self._event_types): -+ if not self._event_types or event.event_type in self._event_types: - self.q.put(event) - - def flush(self): -diff --git a/tests/unittests/test_reporting_hyperv.py b/tests/unittests/test_reporting_hyperv.py -old mode 100644 -new mode 100755 -index 2e64c6c..d01ed5b ---- a/tests/unittests/test_reporting_hyperv.py -+++ b/tests/unittests/test_reporting_hyperv.py -@@ -1,10 +1,12 @@ - # This file is part of cloud-init. See LICENSE file for license information. - - from cloudinit.reporting import events --from cloudinit.reporting import handlers -+from cloudinit.reporting.handlers import HyperVKvpReportingHandler - - import json - import os -+import struct -+import time - - from cloudinit import util - from cloudinit.tests.helpers import CiTestCase -@@ -13,7 +15,7 @@ from cloudinit.tests.helpers import CiTestCase - class TestKvpEncoding(CiTestCase): - def test_encode_decode(self): - kvp = {'key': 'key1', 'value': 'value1'} -- kvp_reporting = handlers.HyperVKvpReportingHandler() -+ kvp_reporting = HyperVKvpReportingHandler() - data = kvp_reporting._encode_kvp_item(kvp['key'], kvp['value']) - self.assertEqual(len(data), kvp_reporting.HV_KVP_RECORD_SIZE) - decoded_kvp = kvp_reporting._decode_kvp_item(data) -@@ -26,57 +28,9 @@ class TextKvpReporter(CiTestCase): - self.tmp_file_path = self.tmp_path('kvp_pool_file') - util.ensure_file(self.tmp_file_path) - -- def test_event_type_can_be_filtered(self): -- reporter = handlers.HyperVKvpReportingHandler( -- kvp_file_path=self.tmp_file_path, -- event_types=['foo', 'bar']) -- -- reporter.publish_event( -- events.ReportingEvent('foo', 'name', 'description')) -- reporter.publish_event( -- events.ReportingEvent('some_other', 'name', 'description3')) -- reporter.q.join() -- -- kvps = list(reporter._iterate_kvps(0)) -- self.assertEqual(1, len(kvps)) -- -- reporter.publish_event( -- events.ReportingEvent('bar', 'name', 'description2')) -- reporter.q.join() -- kvps = list(reporter._iterate_kvps(0)) -- self.assertEqual(2, len(kvps)) -- -- self.assertIn('foo', kvps[0]['key']) -- self.assertIn('bar', kvps[1]['key']) -- self.assertNotIn('some_other', kvps[0]['key']) -- self.assertNotIn('some_other', kvps[1]['key']) -- -- def test_events_are_over_written(self): -- reporter = handlers.HyperVKvpReportingHandler( -- kvp_file_path=self.tmp_file_path) -- -- self.assertEqual(0, len(list(reporter._iterate_kvps(0)))) -- -- reporter.publish_event( -- events.ReportingEvent('foo', 'name1', 'description')) -- reporter.publish_event( -- events.ReportingEvent('foo', 'name2', 'description')) -- reporter.q.join() -- self.assertEqual(2, len(list(reporter._iterate_kvps(0)))) -- -- reporter2 = handlers.HyperVKvpReportingHandler( -- kvp_file_path=self.tmp_file_path) -- reporter2.incarnation_no = reporter.incarnation_no + 1 -- reporter2.publish_event( -- events.ReportingEvent('foo', 'name3', 'description')) -- reporter2.q.join() -- -- self.assertEqual(2, len(list(reporter2._iterate_kvps(0)))) -- - def test_events_with_higher_incarnation_not_over_written(self): -- reporter = handlers.HyperVKvpReportingHandler( -+ reporter = HyperVKvpReportingHandler( - kvp_file_path=self.tmp_file_path) -- - self.assertEqual(0, len(list(reporter._iterate_kvps(0)))) - - reporter.publish_event( -@@ -86,7 +40,7 @@ class TextKvpReporter(CiTestCase): - reporter.q.join() - self.assertEqual(2, len(list(reporter._iterate_kvps(0)))) - -- reporter3 = handlers.HyperVKvpReportingHandler( -+ reporter3 = HyperVKvpReportingHandler( - kvp_file_path=self.tmp_file_path) - reporter3.incarnation_no = reporter.incarnation_no - 1 - reporter3.publish_event( -@@ -95,7 +49,7 @@ class TextKvpReporter(CiTestCase): - self.assertEqual(3, len(list(reporter3._iterate_kvps(0)))) - - def test_finish_event_result_is_logged(self): -- reporter = handlers.HyperVKvpReportingHandler( -+ reporter = HyperVKvpReportingHandler( - kvp_file_path=self.tmp_file_path) - reporter.publish_event( - events.FinishReportingEvent('name2', 'description1', -@@ -105,7 +59,7 @@ class TextKvpReporter(CiTestCase): - - def test_file_operation_issue(self): - os.remove(self.tmp_file_path) -- reporter = handlers.HyperVKvpReportingHandler( -+ reporter = HyperVKvpReportingHandler( - kvp_file_path=self.tmp_file_path) - reporter.publish_event( - events.FinishReportingEvent('name2', 'description1', -@@ -113,7 +67,7 @@ class TextKvpReporter(CiTestCase): - reporter.q.join() - - def test_event_very_long(self): -- reporter = handlers.HyperVKvpReportingHandler( -+ reporter = HyperVKvpReportingHandler( - kvp_file_path=self.tmp_file_path) - description = 'ab' * reporter.HV_KVP_EXCHANGE_MAX_VALUE_SIZE - long_event = events.FinishReportingEvent( -@@ -132,3 +86,43 @@ class TextKvpReporter(CiTestCase): - self.assertEqual(msg_slice['msg_i'], i) - full_description += msg_slice['msg'] - self.assertEqual(description, full_description) -+ -+ def test_not_truncate_kvp_file_modified_after_boot(self): -+ with open(self.tmp_file_path, "wb+") as f: -+ kvp = {'key': 'key1', 'value': 'value1'} -+ data = (struct.pack("%ds%ds" % ( -+ HyperVKvpReportingHandler.HV_KVP_EXCHANGE_MAX_KEY_SIZE, -+ HyperVKvpReportingHandler.HV_KVP_EXCHANGE_MAX_VALUE_SIZE), -+ kvp['key'].encode('utf-8'), kvp['value'].encode('utf-8'))) -+ f.write(data) -+ cur_time = time.time() -+ os.utime(self.tmp_file_path, (cur_time, cur_time)) -+ -+ # reset this because the unit test framework -+ # has already polluted the class variable -+ HyperVKvpReportingHandler._already_truncated_pool_file = False -+ -+ reporter = HyperVKvpReportingHandler(kvp_file_path=self.tmp_file_path) -+ kvps = list(reporter._iterate_kvps(0)) -+ self.assertEqual(1, len(kvps)) -+ -+ def test_truncate_stale_kvp_file(self): -+ with open(self.tmp_file_path, "wb+") as f: -+ kvp = {'key': 'key1', 'value': 'value1'} -+ data = (struct.pack("%ds%ds" % ( -+ HyperVKvpReportingHandler.HV_KVP_EXCHANGE_MAX_KEY_SIZE, -+ HyperVKvpReportingHandler.HV_KVP_EXCHANGE_MAX_VALUE_SIZE), -+ kvp['key'].encode('utf-8'), kvp['value'].encode('utf-8'))) -+ f.write(data) -+ -+ # set the time ways back to make it look like -+ # we had an old kvp file -+ os.utime(self.tmp_file_path, (1000000, 1000000)) -+ -+ # reset this because the unit test framework -+ # has already polluted the class variable -+ HyperVKvpReportingHandler._already_truncated_pool_file = False -+ -+ reporter = HyperVKvpReportingHandler(kvp_file_path=self.tmp_file_path) -+ kvps = list(reporter._iterate_kvps(0)) -+ self.assertEqual(0, len(kvps)) --- -1.8.3.1 - diff --git a/SOURCES/ci-Azure-Ensure-platform-random_seed-is-always-serializ.patch b/SOURCES/ci-Azure-Ensure-platform-random_seed-is-always-serializ.patch deleted file mode 100644 index b77e755..0000000 --- a/SOURCES/ci-Azure-Ensure-platform-random_seed-is-always-serializ.patch +++ /dev/null @@ -1,156 +0,0 @@ -From ae9b545cef4a68dfb9f9356dd27e43ff71ec26aa Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 29 May 2019 13:41:45 +0200 -Subject: [PATCH 1/5] Azure: Ensure platform random_seed is always serializable - as JSON. - -RH-Author: Eduardo Otubo -Message-id: <20190529134149.842-2-otubo@redhat.com> -Patchwork-id: 88272 -O-Subject: [RHEL-8.0.1/RHEL-8.1.0 cloud-init PATCHv2 1/5] Azure: Ensure platform random_seed is always serializable as JSON. -Bugzilla: 1691986 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Cathy Avery - -From: "Jason Zions (MSFT)" -commit 0dc3a77f41f4544e4cb5a41637af7693410d4cdf -Author: Jason Zions (MSFT) -Date: Tue Mar 26 18:53:50 2019 +0000 - - Azure: Ensure platform random_seed is always serializable as JSON. - - The Azure platform surfaces random bytes into /sys via Hyper-V. - Python 2.7 json.dump() raises an exception if asked to convert - a str with non-character content, and python 3.0 json.dump() - won't serialize a "bytes" value. As a result, c-i instance - data is often not written by Azure, making reboots slower (c-i - has to repeat work). - - The random data is base64-encoded and then decoded into a string - (str or unicode depending on the version of Python in use). The - base64 string has just as many bits of entropy, so we're not - throwing away useful "information", but we can be certain - json.dump() will correctly serialize the bits. - -Signed-off-by: Miroslav Rezanina - -Conflicts: - tests/unittests/test_datasource/test_azure.py - Skipped the commit edf052c as it removes support for python-2.6 - -Signed-off-by: Eduardo Otubo ---- - cloudinit/sources/DataSourceAzure.py | 24 +++++++++++++++++++----- - tests/data/azure/non_unicode_random_string | 1 + - tests/unittests/test_datasource/test_azure.py | 24 ++++++++++++++++++++++-- - 3 files changed, 42 insertions(+), 7 deletions(-) - create mode 100644 tests/data/azure/non_unicode_random_string - -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -index 2062ca5..a768b2c 100644 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -54,6 +54,7 @@ REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds" - REPORTED_READY_MARKER_FILE = "/var/lib/cloud/data/reported_ready" - AGENT_SEED_DIR = '/var/lib/waagent' - IMDS_URL = "http://169.254.169.254/metadata/" -+PLATFORM_ENTROPY_SOURCE = "/sys/firmware/acpi/tables/OEM0" - - # List of static scripts and network config artifacts created by - # stock ubuntu suported images. -@@ -195,6 +196,8 @@ if util.is_FreeBSD(): - RESOURCE_DISK_PATH = "/dev/" + res_disk - else: - LOG.debug("resource disk is None") -+ # TODO Find where platform entropy data is surfaced -+ PLATFORM_ENTROPY_SOURCE = None - - BUILTIN_DS_CONFIG = { - 'agent_command': AGENT_START_BUILTIN, -@@ -1100,16 +1103,27 @@ def _check_freebsd_cdrom(cdrom_dev): - return False - - --def _get_random_seed(): -+def _get_random_seed(source=PLATFORM_ENTROPY_SOURCE): - """Return content random seed file if available, otherwise, - return None.""" - # azure / hyper-v provides random data here -- # TODO. find the seed on FreeBSD platform - # now update ds_cfg to reflect contents pass in config -- if util.is_FreeBSD(): -+ if source is None: - return None -- return util.load_file("/sys/firmware/acpi/tables/OEM0", -- quiet=True, decode=False) -+ seed = util.load_file(source, quiet=True, decode=False) -+ -+ # The seed generally contains non-Unicode characters. load_file puts -+ # them into a str (in python 2) or bytes (in python 3). In python 2, -+ # bad octets in a str cause util.json_dumps() to throw an exception. In -+ # python 3, bytes is a non-serializable type, and the handler load_file -+ # uses applies b64 encoding *again* to handle it. The simplest solution -+ # is to just b64encode the data and then decode it to a serializable -+ # string. Same number of bits of entropy, just with 25% more zeroes. -+ # There's no need to undo this base64-encoding when the random seed is -+ # actually used in cc_seed_random.py. -+ seed = base64.b64encode(seed).decode() -+ -+ return seed - - - def list_possible_azure_ds_devs(): -diff --git a/tests/data/azure/non_unicode_random_string b/tests/data/azure/non_unicode_random_string -new file mode 100644 -index 0000000..b9ecefb ---- /dev/null -+++ b/tests/data/azure/non_unicode_random_string -@@ -0,0 +1 @@ -+OEM0d\x00\x00\x00\x01\x80VRTUALMICROSFT\x02\x17\x00\x06MSFT\x97\x00\x00\x00C\xb4{V\xf4X%\x061x\x90\x1c\xfen\x86\xbf~\xf5\x8c\x94&\x88\xed\x84\xf9B\xbd\xd3\xf1\xdb\xee:\xd9\x0fc\x0e\x83(\xbd\xe3'\xfc\x85,\xdf\xf4\x13\x99N\xc5\xf3Y\x1e\xe3\x0b\xa4H\x08J\xb9\xdcdb$ -\ No newline at end of file -diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py -index 417d86a..eacf225 100644 ---- a/tests/unittests/test_datasource/test_azure.py -+++ b/tests/unittests/test_datasource/test_azure.py -@@ -7,11 +7,11 @@ from cloudinit.sources import ( - UNSET, DataSourceAzure as dsaz, InvalidMetaDataException) - from cloudinit.util import (b64e, decode_binary, load_file, write_file, - find_freebsd_part, get_path_dev_freebsd, -- MountFailedError) -+ MountFailedError, json_dumps, load_json) - from cloudinit.version import version_string as vs - from cloudinit.tests.helpers import ( - HttprettyTestCase, CiTestCase, populate_dir, mock, wrap_and_call, -- ExitStack, PY26, SkipTest) -+ ExitStack, PY26, SkipTest, resourceLocation) - - import crypt - import httpretty -@@ -1924,4 +1924,24 @@ class TestWBIsPlatformViable(CiTestCase): - self.logs.getvalue()) - - -+class TestRandomSeed(CiTestCase): -+ """Test proper handling of random_seed""" -+ -+ def test_non_ascii_seed_is_serializable(self): -+ """Pass if a random string from the Azure infrastructure which -+ contains at least one non-Unicode character can be converted to/from -+ JSON without alteration and without throwing an exception. -+ """ -+ path = resourceLocation("azure/non_unicode_random_string") -+ result = dsaz._get_random_seed(path) -+ -+ obj = {'seed': result} -+ try: -+ serialized = json_dumps(obj) -+ deserialized = load_json(serialized) -+ except UnicodeDecodeError: -+ self.fail("Non-serializable random seed returned") -+ -+ self.assertEqual(deserialized['seed'], result) -+ - # vi: ts=4 expandtab --- -1.8.3.1 - diff --git a/SOURCES/ci-Azure-Return-static-fallback-address-as-if-failed-to.patch b/SOURCES/ci-Azure-Return-static-fallback-address-as-if-failed-to.patch deleted file mode 100644 index 78269a0..0000000 --- a/SOURCES/ci-Azure-Return-static-fallback-address-as-if-failed-to.patch +++ /dev/null @@ -1,102 +0,0 @@ -From ae0f85f867714009af7d4ec6c58e30c552bab556 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 3 Jul 2019 13:06:49 +0200 -Subject: [PATCH 2/2] Azure: Return static fallback address as if failed to - find endpoint - -RH-Author: Eduardo Otubo -Message-id: <20190703130649.14511-1-otubo@redhat.com> -Patchwork-id: 89353 -O-Subject: [RHEL-8.0.1/RHEL-8.1.0 cloud-init PATCH] Azure: Return static fallback address as if failed to find endpoint -Bugzilla: 1691986 -RH-Acked-by: Bandan Das -RH-Acked-by: Mohammed Gamal - -commit ade77012c8bbcd215b7e26065981194ce1b6a157 -Author: Jason Zions (MSFT) -Date: Fri May 10 18:38:55 2019 +0000 - - Azure: Return static fallback address as if failed to find endpoint - - The Azure data source helper attempts to use information in the dhcp - lease to find the Wireserver endpoint (IP address). Under some unusual - circumstances, those attempts will fail. This change uses a static - address, known to be always correct in the Azure public and sovereign - clouds, when the helper fails to locate a valid dhcp lease. This - address is not guaranteed to be correct in Azure Stack environments; - it's still best to use the information from the lease whenever possible. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/sources/helpers/azure.py | 14 +++++++++++--- - tests/unittests/test_datasource/test_azure_helper.py | 9 +++++++-- - 2 files changed, 18 insertions(+), 5 deletions(-) - -diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py -index d3af05e..82c4c8c 100755 ---- a/cloudinit/sources/helpers/azure.py -+++ b/cloudinit/sources/helpers/azure.py -@@ -20,6 +20,9 @@ from cloudinit.reporting import events - - LOG = logging.getLogger(__name__) - -+# This endpoint matches the format as found in dhcp lease files, since this -+# value is applied if the endpoint can't be found within a lease file -+DEFAULT_WIRESERVER_ENDPOINT = "a8:3f:81:10" - - azure_ds_reporter = events.ReportEventStack( - name="azure-ds", -@@ -297,7 +300,12 @@ class WALinuxAgentShim(object): - @azure_ds_telemetry_reporter - def _get_value_from_leases_file(fallback_lease_file): - leases = [] -- content = util.load_file(fallback_lease_file) -+ try: -+ content = util.load_file(fallback_lease_file) -+ except IOError as ex: -+ LOG.error("Failed to read %s: %s", fallback_lease_file, ex) -+ return None -+ - LOG.debug("content is %s", content) - option_name = _get_dhcp_endpoint_option_name() - for line in content.splitlines(): -@@ -372,9 +380,9 @@ class WALinuxAgentShim(object): - fallback_lease_file) - value = WALinuxAgentShim._get_value_from_leases_file( - fallback_lease_file) -- - if value is None: -- raise ValueError('No endpoint found.') -+ LOG.warning("No lease found; using default endpoint") -+ value = DEFAULT_WIRESERVER_ENDPOINT - - endpoint_ip_address = WALinuxAgentShim.get_ip_from_lease_value(value) - LOG.debug('Azure endpoint found at %s', endpoint_ip_address) -diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py -index 0255616..bd006ab 100644 ---- a/tests/unittests/test_datasource/test_azure_helper.py -+++ b/tests/unittests/test_datasource/test_azure_helper.py -@@ -67,12 +67,17 @@ class TestFindEndpoint(CiTestCase): - self.networkd_leases.return_value = None - - def test_missing_file(self): -- self.assertRaises(ValueError, wa_shim.find_endpoint) -+ """wa_shim find_endpoint uses default endpoint if leasefile not found -+ """ -+ self.assertEqual(wa_shim.find_endpoint(), "168.63.129.16") - - def test_missing_special_azure_line(self): -+ """wa_shim find_endpoint uses default endpoint if leasefile is found -+ but does not contain DHCP Option 245 (whose value is the endpoint) -+ """ - self.load_file.return_value = '' - self.dhcp_options.return_value = {'eth0': {'key': 'value'}} -- self.assertRaises(ValueError, wa_shim.find_endpoint) -+ self.assertEqual(wa_shim.find_endpoint(), "168.63.129.16") - - @staticmethod - def _build_lease_content(encoded_address): --- -1.8.3.1 - diff --git a/SOURCES/ci-Change-from-redhat-to-rhel-in-systemd-generator-tmpl.patch b/SOURCES/ci-Change-from-redhat-to-rhel-in-systemd-generator-tmpl.patch new file mode 100644 index 0000000..ebc2d05 --- /dev/null +++ b/SOURCES/ci-Change-from-redhat-to-rhel-in-systemd-generator-tmpl.patch @@ -0,0 +1,46 @@ +From f9cc7310eccee8076aea069e86ca137eb1002d19 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Wed, 24 Jun 2020 07:55:01 +0200 +Subject: [PATCH 2/3] Change from redhat to rhel in systemd generator tmpl + (#450) + +RH-Author: Eduardo Otubo +Message-id: <20200623154034.28563-3-otubo@redhat.com> +Patchwork-id: 97783 +O-Subject: [RHEL-8.3.0/RHEL-8.2.1 cloud-init PATCH 2/3] Change from redhat to rhel in systemd generator tmpl (#450) +Bugzilla: 1844366 +RH-Acked-by: Cathy Avery +RH-Acked-by: Mohammed Gamal + +commit 650d53d656b612442773453813d8417b234d3752 +Author: Eduardo Otubo +Date: Tue Jun 23 14:41:15 2020 +0200 + + Change from redhat to rhel in systemd generator tmpl (#450) + + The name `redhat' is not used but rather `rhel' to identify the distro. + + Signed-off-by: Eduardo Otubo + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + systemd/cloud-init-generator.tmpl | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/systemd/cloud-init-generator.tmpl b/systemd/cloud-init-generator.tmpl +index 45efa24..0773356 100755 +--- a/systemd/cloud-init-generator.tmpl ++++ b/systemd/cloud-init-generator.tmpl +@@ -83,7 +83,7 @@ default() { + + check_for_datasource() { + local ds_rc="" +-{% if variant in ["redhat", "fedora", "centos"] %} ++{% if variant in ["rhel", "fedora", "centos"] %} + local dsidentify="/usr/libexec/cloud-init/ds-identify" + {% else %} + local dsidentify="/usr/lib/cloud-init/ds-identify" +-- +1.8.3.1 + diff --git a/SOURCES/ci-DataSourceAzure-Adjust-timeout-for-polling-IMDS.patch b/SOURCES/ci-DataSourceAzure-Adjust-timeout-for-polling-IMDS.patch deleted file mode 100644 index afa3324..0000000 --- a/SOURCES/ci-DataSourceAzure-Adjust-timeout-for-polling-IMDS.patch +++ /dev/null @@ -1,111 +0,0 @@ -From b2500e258b930479cef36f514fcf9581ba68c976 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 29 May 2019 13:41:48 +0200 -Subject: [PATCH 4/5] DataSourceAzure: Adjust timeout for polling IMDS - -RH-Author: Eduardo Otubo -Message-id: <20190529134149.842-5-otubo@redhat.com> -Patchwork-id: 88267 -O-Subject: [RHEL-8.0.1/RHEL-8.1.0 cloud-init PATCHv2 4/5] DataSourceAzure: Adjust timeout for polling IMDS -Bugzilla: 1691986 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Cathy Avery - -From: Anh Vo -commit ab6621d849b24bb652243e88c79f6f3b446048d7 -Author: Anh Vo -Date: Wed May 8 14:54:03 2019 +0000 - - DataSourceAzure: Adjust timeout for polling IMDS - - If the IMDS primary server is not available, falling back to the - secondary server takes about 1s. The net result is that the - expected E2E time is slightly more than 1s. This change increases - the timeout to 2s to prevent the infinite loop of timeouts. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/sources/DataSourceAzure.py | 15 ++++++++++----- - tests/unittests/test_datasource/test_azure.py | 10 +++++++--- - 2 files changed, 17 insertions(+), 8 deletions(-) - -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -index c827816..5baf8da 100755 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -57,7 +57,12 @@ AZURE_CHASSIS_ASSET_TAG = '7783-7084-3265-9085-8269-3286-77' - REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds" - REPORTED_READY_MARKER_FILE = "/var/lib/cloud/data/reported_ready" - AGENT_SEED_DIR = '/var/lib/waagent' -+ -+# In the event where the IMDS primary server is not -+# available, it takes 1s to fallback to the secondary one -+IMDS_TIMEOUT_IN_SECONDS = 2 - IMDS_URL = "http://169.254.169.254/metadata/" -+ - PLATFORM_ENTROPY_SOURCE = "/sys/firmware/acpi/tables/OEM0" - - # List of static scripts and network config artifacts created by -@@ -582,9 +587,9 @@ class DataSourceAzure(sources.DataSource): - return - self._ephemeral_dhcp_ctx.clean_network() - else: -- return readurl(url, timeout=1, headers=headers, -- exception_cb=exc_cb, infinite=True, -- log_req_resp=False).contents -+ return readurl(url, timeout=IMDS_TIMEOUT_IN_SECONDS, -+ headers=headers, exception_cb=exc_cb, -+ infinite=True, log_req_resp=False).contents - except UrlError: - # Teardown our EphemeralDHCPv4 context on failure as we retry - self._ephemeral_dhcp_ctx.clean_network() -@@ -1291,8 +1296,8 @@ def _get_metadata_from_imds(retries): - headers = {"Metadata": "true"} - try: - response = readurl( -- url, timeout=1, headers=headers, retries=retries, -- exception_cb=retry_on_url_exc) -+ url, timeout=IMDS_TIMEOUT_IN_SECONDS, headers=headers, -+ retries=retries, exception_cb=retry_on_url_exc) - except Exception as e: - LOG.debug('Ignoring IMDS instance metadata: %s', e) - return {} -diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py -index eacf225..bc8b42c 100644 ---- a/tests/unittests/test_datasource/test_azure.py -+++ b/tests/unittests/test_datasource/test_azure.py -@@ -163,7 +163,8 @@ class TestGetMetadataFromIMDS(HttprettyTestCase): - - m_readurl.assert_called_with( - self.network_md_url, exception_cb=mock.ANY, -- headers={'Metadata': 'true'}, retries=2, timeout=1) -+ headers={'Metadata': 'true'}, retries=2, -+ timeout=dsaz.IMDS_TIMEOUT_IN_SECONDS) - - @mock.patch('cloudinit.url_helper.time.sleep') - @mock.patch(MOCKPATH + 'net.is_up') -@@ -1789,7 +1790,8 @@ class TestAzureDataSourcePreprovisioning(CiTestCase): - headers={'Metadata': 'true', - 'User-Agent': - 'Cloud-Init/%s' % vs() -- }, method='GET', timeout=1, -+ }, method='GET', -+ timeout=dsaz.IMDS_TIMEOUT_IN_SECONDS, - url=full_url)]) - self.assertEqual(m_dhcp.call_count, 2) - m_net.assert_any_call( -@@ -1826,7 +1828,9 @@ class TestAzureDataSourcePreprovisioning(CiTestCase): - headers={'Metadata': 'true', - 'User-Agent': - 'Cloud-Init/%s' % vs()}, -- method='GET', timeout=1, url=full_url)]) -+ method='GET', -+ timeout=dsaz.IMDS_TIMEOUT_IN_SECONDS, -+ url=full_url)]) - self.assertEqual(m_dhcp.call_count, 2) - m_net.assert_any_call( - broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9', --- -1.8.3.1 - diff --git a/SOURCES/ci-DatasourceAzure-add-additional-logging-for-azure-dat.patch b/SOURCES/ci-DatasourceAzure-add-additional-logging-for-azure-dat.patch deleted file mode 100644 index ea58c58..0000000 --- a/SOURCES/ci-DatasourceAzure-add-additional-logging-for-azure-dat.patch +++ /dev/null @@ -1,642 +0,0 @@ -From 4e4e73df5ce7a819f48bed0ae6d2f0b1fb7e243b Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 29 May 2019 13:41:46 +0200 -Subject: [PATCH 2/5] DatasourceAzure: add additional logging for azure - datasource - -RH-Author: Eduardo Otubo -Message-id: <20190529134149.842-3-otubo@redhat.com> -Patchwork-id: 88268 -O-Subject: [RHEL-8.0.1/RHEL-8.1.0 cloud-init PATCHv2 2/5] DatasourceAzure: add additional logging for azure datasource -Bugzilla: 1691986 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Cathy Avery - -From: Anh Vo -commit 0d8c88393b51db6454491a379dcc2e691551217a -Author: Anh Vo -Date: Wed Apr 3 18:23:18 2019 +0000 - - DatasourceAzure: add additional logging for azure datasource - - Create an Azure logging decorator and use additional ReportEventStack - context managers to provide additional logging details. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/sources/DataSourceAzure.py | 231 ++++++++++++++++++++++------------- - cloudinit/sources/helpers/azure.py | 31 +++++ - 2 files changed, 179 insertions(+), 83 deletions(-) - mode change 100644 => 100755 cloudinit/sources/DataSourceAzure.py - mode change 100644 => 100755 cloudinit/sources/helpers/azure.py - -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -old mode 100644 -new mode 100755 -index a768b2c..c827816 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -21,10 +21,14 @@ from cloudinit import net - from cloudinit.event import EventType - from cloudinit.net.dhcp import EphemeralDHCPv4 - from cloudinit import sources --from cloudinit.sources.helpers.azure import get_metadata_from_fabric - from cloudinit.sources.helpers import netlink - from cloudinit.url_helper import UrlError, readurl, retry_on_url_exc - from cloudinit import util -+from cloudinit.reporting import events -+ -+from cloudinit.sources.helpers.azure import (azure_ds_reporter, -+ azure_ds_telemetry_reporter, -+ get_metadata_from_fabric) - - LOG = logging.getLogger(__name__) - -@@ -244,6 +248,7 @@ def set_hostname(hostname, hostname_command='hostname'): - util.subp(['hostnamectl', 'set-hostname', str(hostname)]) - - -+@azure_ds_telemetry_reporter - @contextlib.contextmanager - def temporary_hostname(temp_hostname, cfg, hostname_command='hostname'): - """ -@@ -290,6 +295,7 @@ class DataSourceAzure(sources.DataSource): - root = sources.DataSource.__str__(self) - return "%s [seed=%s]" % (root, self.seed) - -+ @azure_ds_telemetry_reporter - def bounce_network_with_azure_hostname(self): - # When using cloud-init to provision, we have to set the hostname from - # the metadata and "bounce" the network to force DDNS to update via -@@ -315,6 +321,7 @@ class DataSourceAzure(sources.DataSource): - util.logexc(LOG, "handling set_hostname failed") - return False - -+ @azure_ds_telemetry_reporter - def get_metadata_from_agent(self): - temp_hostname = self.metadata.get('local-hostname') - agent_cmd = self.ds_cfg['agent_command'] -@@ -344,15 +351,18 @@ class DataSourceAzure(sources.DataSource): - LOG.debug("ssh authentication: " - "using fingerprint from fabirc") - -- # wait very long for public SSH keys to arrive -- # https://bugs.launchpad.net/cloud-init/+bug/1717611 -- missing = util.log_time(logfunc=LOG.debug, -- msg="waiting for SSH public key files", -- func=util.wait_for_files, -- args=(fp_files, 900)) -- -- if len(missing): -- LOG.warning("Did not find files, but going on: %s", missing) -+ with events.ReportEventStack( -+ name="waiting-for-ssh-public-key", -+ description="wait for agents to retrieve ssh keys", -+ parent=azure_ds_reporter): -+ # wait very long for public SSH keys to arrive -+ # https://bugs.launchpad.net/cloud-init/+bug/1717611 -+ missing = util.log_time(logfunc=LOG.debug, -+ msg="waiting for SSH public key files", -+ func=util.wait_for_files, -+ args=(fp_files, 900)) -+ if len(missing): -+ LOG.warning("Did not find files, but going on: %s", missing) - - metadata = {} - metadata['public-keys'] = key_value or pubkeys_from_crt_files(fp_files) -@@ -366,6 +376,7 @@ class DataSourceAzure(sources.DataSource): - subplatform_type = 'seed-dir' - return '%s (%s)' % (subplatform_type, self.seed) - -+ @azure_ds_telemetry_reporter - def crawl_metadata(self): - """Walk all instance metadata sources returning a dict on success. - -@@ -467,6 +478,7 @@ class DataSourceAzure(sources.DataSource): - super(DataSourceAzure, self).clear_cached_attrs(attr_defaults) - self._metadata_imds = sources.UNSET - -+ @azure_ds_telemetry_reporter - def _get_data(self): - """Crawl and process datasource metadata caching metadata as attrs. - -@@ -513,6 +525,7 @@ class DataSourceAzure(sources.DataSource): - # quickly (local check only) if self.instance_id is still valid - return sources.instance_id_matches_system_uuid(self.get_instance_id()) - -+ @azure_ds_telemetry_reporter - def setup(self, is_new_instance): - if self._negotiated is False: - LOG.debug("negotiating for %s (new_instance=%s)", -@@ -580,6 +593,7 @@ class DataSourceAzure(sources.DataSource): - if nl_sock: - nl_sock.close() - -+ @azure_ds_telemetry_reporter - def _report_ready(self, lease): - """Tells the fabric provisioning has completed """ - try: -@@ -617,9 +631,14 @@ class DataSourceAzure(sources.DataSource): - def _reprovision(self): - """Initiate the reprovisioning workflow.""" - contents = self._poll_imds() -- md, ud, cfg = read_azure_ovf(contents) -- return (md, ud, cfg, {'ovf-env.xml': contents}) -- -+ with events.ReportEventStack( -+ name="reprovisioning-read-azure-ovf", -+ description="read azure ovf during reprovisioning", -+ parent=azure_ds_reporter): -+ md, ud, cfg = read_azure_ovf(contents) -+ return (md, ud, cfg, {'ovf-env.xml': contents}) -+ -+ @azure_ds_telemetry_reporter - def _negotiate(self): - """Negotiate with fabric and return data from it. - -@@ -652,6 +671,7 @@ class DataSourceAzure(sources.DataSource): - util.del_file(REPROVISION_MARKER_FILE) - return fabric_data - -+ @azure_ds_telemetry_reporter - def activate(self, cfg, is_new_instance): - address_ephemeral_resize(is_new_instance=is_new_instance, - preserve_ntfs=self.ds_cfg.get( -@@ -690,12 +710,14 @@ def _partitions_on_device(devpath, maxnum=16): - return [] - - -+@azure_ds_telemetry_reporter - def _has_ntfs_filesystem(devpath): - ntfs_devices = util.find_devs_with("TYPE=ntfs", no_cache=True) - LOG.debug('ntfs_devices found = %s', ntfs_devices) - return os.path.realpath(devpath) in ntfs_devices - - -+@azure_ds_telemetry_reporter - def can_dev_be_reformatted(devpath, preserve_ntfs): - """Determine if the ephemeral drive at devpath should be reformatted. - -@@ -744,43 +766,59 @@ def can_dev_be_reformatted(devpath, preserve_ntfs): - (cand_part, cand_path, devpath)) - return False, msg - -+ @azure_ds_telemetry_reporter - def count_files(mp): - ignored = set(['dataloss_warning_readme.txt']) - return len([f for f in os.listdir(mp) if f.lower() not in ignored]) - - bmsg = ('partition %s (%s) on device %s was ntfs formatted' % - (cand_part, cand_path, devpath)) -- try: -- file_count = util.mount_cb(cand_path, count_files, mtype="ntfs", -- update_env_for_mount={'LANG': 'C'}) -- except util.MountFailedError as e: -- if "unknown filesystem type 'ntfs'" in str(e): -- return True, (bmsg + ' but this system cannot mount NTFS,' -- ' assuming there are no important files.' -- ' Formatting allowed.') -- return False, bmsg + ' but mount of %s failed: %s' % (cand_part, e) -- -- if file_count != 0: -- LOG.warning("it looks like you're using NTFS on the ephemeral disk, " -- 'to ensure that filesystem does not get wiped, set ' -- '%s.%s in config', '.'.join(DS_CFG_PATH), -- DS_CFG_KEY_PRESERVE_NTFS) -- return False, bmsg + ' but had %d files on it.' % file_count -+ -+ with events.ReportEventStack( -+ name="mount-ntfs-and-count", -+ description="mount-ntfs-and-count", -+ parent=azure_ds_reporter) as evt: -+ try: -+ file_count = util.mount_cb(cand_path, count_files, mtype="ntfs", -+ update_env_for_mount={'LANG': 'C'}) -+ except util.MountFailedError as e: -+ evt.description = "cannot mount ntfs" -+ if "unknown filesystem type 'ntfs'" in str(e): -+ return True, (bmsg + ' but this system cannot mount NTFS,' -+ ' assuming there are no important files.' -+ ' Formatting allowed.') -+ return False, bmsg + ' but mount of %s failed: %s' % (cand_part, e) -+ -+ if file_count != 0: -+ evt.description = "mounted and counted %d files" % file_count -+ LOG.warning("it looks like you're using NTFS on the ephemeral" -+ " disk, to ensure that filesystem does not get wiped," -+ " set %s.%s in config", '.'.join(DS_CFG_PATH), -+ DS_CFG_KEY_PRESERVE_NTFS) -+ return False, bmsg + ' but had %d files on it.' % file_count - - return True, bmsg + ' and had no important files. Safe for reformatting.' - - -+@azure_ds_telemetry_reporter - def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120, - is_new_instance=False, preserve_ntfs=False): - # wait for ephemeral disk to come up - naplen = .2 -- missing = util.wait_for_files([devpath], maxwait=maxwait, naplen=naplen, -- log_pre="Azure ephemeral disk: ") -- -- if missing: -- LOG.warning("ephemeral device '%s' did not appear after %d seconds.", -- devpath, maxwait) -- return -+ with events.ReportEventStack( -+ name="wait-for-ephemeral-disk", -+ description="wait for ephemeral disk", -+ parent=azure_ds_reporter): -+ missing = util.wait_for_files([devpath], -+ maxwait=maxwait, -+ naplen=naplen, -+ log_pre="Azure ephemeral disk: ") -+ -+ if missing: -+ LOG.warning("ephemeral device '%s' did" -+ " not appear after %d seconds.", -+ devpath, maxwait) -+ return - - result = False - msg = None -@@ -808,6 +846,7 @@ def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120, - return - - -+@azure_ds_telemetry_reporter - def perform_hostname_bounce(hostname, cfg, prev_hostname): - # set the hostname to 'hostname' if it is not already set to that. - # then, if policy is not off, bounce the interface using command -@@ -843,6 +882,7 @@ def perform_hostname_bounce(hostname, cfg, prev_hostname): - return True - - -+@azure_ds_telemetry_reporter - def crtfile_to_pubkey(fname, data=None): - pipeline = ('openssl x509 -noout -pubkey < "$0" |' - 'ssh-keygen -i -m PKCS8 -f /dev/stdin') -@@ -851,6 +891,7 @@ def crtfile_to_pubkey(fname, data=None): - return out.rstrip() - - -+@azure_ds_telemetry_reporter - def pubkeys_from_crt_files(flist): - pubkeys = [] - errors = [] -@@ -866,6 +907,7 @@ def pubkeys_from_crt_files(flist): - return pubkeys - - -+@azure_ds_telemetry_reporter - def write_files(datadir, files, dirmode=None): - - def _redact_password(cnt, fname): -@@ -893,6 +935,7 @@ def write_files(datadir, files, dirmode=None): - util.write_file(filename=fname, content=content, mode=0o600) - - -+@azure_ds_telemetry_reporter - def invoke_agent(cmd): - # this is a function itself to simplify patching it for test - if cmd: -@@ -912,6 +955,7 @@ def find_child(node, filter_func): - return ret - - -+@azure_ds_telemetry_reporter - def load_azure_ovf_pubkeys(sshnode): - # This parses a 'SSH' node formatted like below, and returns - # an array of dicts. -@@ -964,6 +1008,7 @@ def load_azure_ovf_pubkeys(sshnode): - return found - - -+@azure_ds_telemetry_reporter - def read_azure_ovf(contents): - try: - dom = minidom.parseString(contents) -@@ -1064,6 +1109,7 @@ def read_azure_ovf(contents): - return (md, ud, cfg) - - -+@azure_ds_telemetry_reporter - def _extract_preprovisioned_vm_setting(dom): - """Read the preprovision flag from the ovf. It should not - exist unless true.""" -@@ -1092,6 +1138,7 @@ def encrypt_pass(password, salt_id="$6$"): - return crypt.crypt(password, salt_id + util.rand_str(strlen=16)) - - -+@azure_ds_telemetry_reporter - def _check_freebsd_cdrom(cdrom_dev): - """Return boolean indicating path to cdrom device has content.""" - try: -@@ -1103,6 +1150,7 @@ def _check_freebsd_cdrom(cdrom_dev): - return False - - -+@azure_ds_telemetry_reporter - def _get_random_seed(source=PLATFORM_ENTROPY_SOURCE): - """Return content random seed file if available, otherwise, - return None.""" -@@ -1126,6 +1174,7 @@ def _get_random_seed(source=PLATFORM_ENTROPY_SOURCE): - return seed - - -+@azure_ds_telemetry_reporter - def list_possible_azure_ds_devs(): - devlist = [] - if util.is_FreeBSD(): -@@ -1140,6 +1189,7 @@ def list_possible_azure_ds_devs(): - return devlist - - -+@azure_ds_telemetry_reporter - def load_azure_ds_dir(source_dir): - ovf_file = os.path.join(source_dir, "ovf-env.xml") - -@@ -1162,47 +1212,54 @@ def parse_network_config(imds_metadata): - @param: imds_metadata: Dict of content read from IMDS network service. - @return: Dictionary containing network version 2 standard configuration. - """ -- if imds_metadata != sources.UNSET and imds_metadata: -- netconfig = {'version': 2, 'ethernets': {}} -- LOG.debug('Azure: generating network configuration from IMDS') -- network_metadata = imds_metadata['network'] -- for idx, intf in enumerate(network_metadata['interface']): -- nicname = 'eth{idx}'.format(idx=idx) -- dev_config = {} -- for addr4 in intf['ipv4']['ipAddress']: -- privateIpv4 = addr4['privateIpAddress'] -- if privateIpv4: -- if dev_config.get('dhcp4', False): -- # Append static address config for nic > 1 -- netPrefix = intf['ipv4']['subnet'][0].get( -- 'prefix', '24') -- if not dev_config.get('addresses'): -- dev_config['addresses'] = [] -- dev_config['addresses'].append( -- '{ip}/{prefix}'.format( -- ip=privateIpv4, prefix=netPrefix)) -- else: -- dev_config['dhcp4'] = True -- for addr6 in intf['ipv6']['ipAddress']: -- privateIpv6 = addr6['privateIpAddress'] -- if privateIpv6: -- dev_config['dhcp6'] = True -- break -- if dev_config: -- mac = ':'.join(re.findall(r'..', intf['macAddress'])) -- dev_config.update( -- {'match': {'macaddress': mac.lower()}, -- 'set-name': nicname}) -- netconfig['ethernets'][nicname] = dev_config -- else: -- blacklist = ['mlx4_core'] -- LOG.debug('Azure: generating fallback configuration') -- # generate a network config, blacklist picking mlx4_core devs -- netconfig = net.generate_fallback_config( -- blacklist_drivers=blacklist, config_driver=True) -- return netconfig -+ with events.ReportEventStack( -+ name="parse_network_config", -+ description="", -+ parent=azure_ds_reporter) as evt: -+ if imds_metadata != sources.UNSET and imds_metadata: -+ netconfig = {'version': 2, 'ethernets': {}} -+ LOG.debug('Azure: generating network configuration from IMDS') -+ network_metadata = imds_metadata['network'] -+ for idx, intf in enumerate(network_metadata['interface']): -+ nicname = 'eth{idx}'.format(idx=idx) -+ dev_config = {} -+ for addr4 in intf['ipv4']['ipAddress']: -+ privateIpv4 = addr4['privateIpAddress'] -+ if privateIpv4: -+ if dev_config.get('dhcp4', False): -+ # Append static address config for nic > 1 -+ netPrefix = intf['ipv4']['subnet'][0].get( -+ 'prefix', '24') -+ if not dev_config.get('addresses'): -+ dev_config['addresses'] = [] -+ dev_config['addresses'].append( -+ '{ip}/{prefix}'.format( -+ ip=privateIpv4, prefix=netPrefix)) -+ else: -+ dev_config['dhcp4'] = True -+ for addr6 in intf['ipv6']['ipAddress']: -+ privateIpv6 = addr6['privateIpAddress'] -+ if privateIpv6: -+ dev_config['dhcp6'] = True -+ break -+ if dev_config: -+ mac = ':'.join(re.findall(r'..', intf['macAddress'])) -+ dev_config.update( -+ {'match': {'macaddress': mac.lower()}, -+ 'set-name': nicname}) -+ netconfig['ethernets'][nicname] = dev_config -+ evt.description = "network config from imds" -+ else: -+ blacklist = ['mlx4_core'] -+ LOG.debug('Azure: generating fallback configuration') -+ # generate a network config, blacklist picking mlx4_core devs -+ netconfig = net.generate_fallback_config( -+ blacklist_drivers=blacklist, config_driver=True) -+ evt.description = "network config from fallback" -+ return netconfig - - -+@azure_ds_telemetry_reporter - def get_metadata_from_imds(fallback_nic, retries): - """Query Azure's network metadata service, returning a dictionary. - -@@ -1227,6 +1284,7 @@ def get_metadata_from_imds(fallback_nic, retries): - return util.log_time(**kwargs) - - -+@azure_ds_telemetry_reporter - def _get_metadata_from_imds(retries): - - url = IMDS_URL + "instance?api-version=2017-12-01" -@@ -1246,6 +1304,7 @@ def _get_metadata_from_imds(retries): - return {} - - -+@azure_ds_telemetry_reporter - def maybe_remove_ubuntu_network_config_scripts(paths=None): - """Remove Azure-specific ubuntu network config for non-primary nics. - -@@ -1283,14 +1342,20 @@ def maybe_remove_ubuntu_network_config_scripts(paths=None): - - - def _is_platform_viable(seed_dir): -- """Check platform environment to report if this datasource may run.""" -- asset_tag = util.read_dmi_data('chassis-asset-tag') -- if asset_tag == AZURE_CHASSIS_ASSET_TAG: -- return True -- LOG.debug("Non-Azure DMI asset tag '%s' discovered.", asset_tag) -- if os.path.exists(os.path.join(seed_dir, 'ovf-env.xml')): -- return True -- return False -+ with events.ReportEventStack( -+ name="check-platform-viability", -+ description="found azure asset tag", -+ parent=azure_ds_reporter) as evt: -+ -+ """Check platform environment to report if this datasource may run.""" -+ asset_tag = util.read_dmi_data('chassis-asset-tag') -+ if asset_tag == AZURE_CHASSIS_ASSET_TAG: -+ return True -+ LOG.debug("Non-Azure DMI asset tag '%s' discovered.", asset_tag) -+ evt.description = "Non-Azure DMI asset tag '%s' discovered.", asset_tag -+ if os.path.exists(os.path.join(seed_dir, 'ovf-env.xml')): -+ return True -+ return False - - - class BrokenAzureDataSource(Exception): -diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py -old mode 100644 -new mode 100755 -index 2829dd2..d3af05e ---- a/cloudinit/sources/helpers/azure.py -+++ b/cloudinit/sources/helpers/azure.py -@@ -16,10 +16,27 @@ from xml.etree import ElementTree - - from cloudinit import url_helper - from cloudinit import util -+from cloudinit.reporting import events - - LOG = logging.getLogger(__name__) - - -+azure_ds_reporter = events.ReportEventStack( -+ name="azure-ds", -+ description="initialize reporter for azure ds", -+ reporting_enabled=True) -+ -+ -+def azure_ds_telemetry_reporter(func): -+ def impl(*args, **kwargs): -+ with events.ReportEventStack( -+ name=func.__name__, -+ description=func.__name__, -+ parent=azure_ds_reporter): -+ return func(*args, **kwargs) -+ return impl -+ -+ - @contextmanager - def cd(newdir): - prevdir = os.getcwd() -@@ -119,6 +136,7 @@ class OpenSSLManager(object): - def clean_up(self): - util.del_dir(self.tmpdir) - -+ @azure_ds_telemetry_reporter - def generate_certificate(self): - LOG.debug('Generating certificate for communication with fabric...') - if self.certificate is not None: -@@ -139,17 +157,20 @@ class OpenSSLManager(object): - LOG.debug('New certificate generated.') - - @staticmethod -+ @azure_ds_telemetry_reporter - def _run_x509_action(action, cert): - cmd = ['openssl', 'x509', '-noout', action] - result, _ = util.subp(cmd, data=cert) - return result - -+ @azure_ds_telemetry_reporter - def _get_ssh_key_from_cert(self, certificate): - pub_key = self._run_x509_action('-pubkey', certificate) - keygen_cmd = ['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin'] - ssh_key, _ = util.subp(keygen_cmd, data=pub_key) - return ssh_key - -+ @azure_ds_telemetry_reporter - def _get_fingerprint_from_cert(self, certificate): - """openssl x509 formats fingerprints as so: - 'SHA1 Fingerprint=07:3E:19:D1:4D:1C:79:92:24:C6:A0:FD:8D:DA:\ -@@ -163,6 +184,7 @@ class OpenSSLManager(object): - octets = raw_fp[eq+1:-1].split(':') - return ''.join(octets) - -+ @azure_ds_telemetry_reporter - def _decrypt_certs_from_xml(self, certificates_xml): - """Decrypt the certificates XML document using the our private key; - return the list of certs and private keys contained in the doc. -@@ -185,6 +207,7 @@ class OpenSSLManager(object): - shell=True, data=b'\n'.join(lines)) - return out - -+ @azure_ds_telemetry_reporter - def parse_certificates(self, certificates_xml): - """Given the Certificates XML document, return a dictionary of - fingerprints and associated SSH keys derived from the certs.""" -@@ -265,11 +288,13 @@ class WALinuxAgentShim(object): - return socket.inet_ntoa(packed_bytes) - - @staticmethod -+ @azure_ds_telemetry_reporter - def _networkd_get_value_from_leases(leases_d=None): - return dhcp.networkd_get_option_from_leases( - 'OPTION_245', leases_d=leases_d) - - @staticmethod -+ @azure_ds_telemetry_reporter - def _get_value_from_leases_file(fallback_lease_file): - leases = [] - content = util.load_file(fallback_lease_file) -@@ -287,6 +312,7 @@ class WALinuxAgentShim(object): - return leases[-1] - - @staticmethod -+ @azure_ds_telemetry_reporter - def _load_dhclient_json(): - dhcp_options = {} - hooks_dir = WALinuxAgentShim._get_hooks_dir() -@@ -305,6 +331,7 @@ class WALinuxAgentShim(object): - return dhcp_options - - @staticmethod -+ @azure_ds_telemetry_reporter - def _get_value_from_dhcpoptions(dhcp_options): - if dhcp_options is None: - return None -@@ -318,6 +345,7 @@ class WALinuxAgentShim(object): - return _value - - @staticmethod -+ @azure_ds_telemetry_reporter - def find_endpoint(fallback_lease_file=None, dhcp245=None): - value = None - if dhcp245 is not None: -@@ -352,6 +380,7 @@ class WALinuxAgentShim(object): - LOG.debug('Azure endpoint found at %s', endpoint_ip_address) - return endpoint_ip_address - -+ @azure_ds_telemetry_reporter - def register_with_azure_and_fetch_data(self, pubkey_info=None): - if self.openssl_manager is None: - self.openssl_manager = OpenSSLManager() -@@ -404,6 +433,7 @@ class WALinuxAgentShim(object): - - return keys - -+ @azure_ds_telemetry_reporter - def _report_ready(self, goal_state, http_client): - LOG.debug('Reporting ready to Azure fabric.') - document = self.REPORT_READY_XML_TEMPLATE.format( -@@ -419,6 +449,7 @@ class WALinuxAgentShim(object): - LOG.info('Reported ready to Azure fabric.') - - -+@azure_ds_telemetry_reporter - def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None, - pubkey_info=None): - shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file, --- -1.8.3.1 - diff --git a/SOURCES/ci-Enable-ssh_deletekeys-by-default.patch b/SOURCES/ci-Enable-ssh_deletekeys-by-default.patch new file mode 100644 index 0000000..9b7dcd4 --- /dev/null +++ b/SOURCES/ci-Enable-ssh_deletekeys-by-default.patch @@ -0,0 +1,40 @@ +From be355ece2812bb19ac5af4262bcf84a2b758eefd Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Mon, 13 Jul 2020 10:19:40 +0200 +Subject: [PATCH] Enable ssh_deletekeys by default + +RH-Author: Eduardo Otubo +Message-id: <20200710085556.6993-1-otubo@redhat.com> +Patchwork-id: 97933 +O-Subject: [RHEL-8.2.1 cloud-init PATCH] Enable ssh_deletekeys by default +Bugzilla: 1855595 +Acked-by: Miroslav Rezanina + +The configuration option ssh_deletekeys will trigger the generation +of new ssh keys for every new instance deployed. + +x-downstream-only: yes +resolves: rhbz#1814152 + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + rhel/cloud.cfg | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg +index 82e8bf6..9ecba21 100644 +--- a/rhel/cloud.cfg ++++ b/rhel/cloud.cfg +@@ -6,7 +6,7 @@ ssh_pwauth: 0 + + mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service', '0', '2'] + resize_rootfs_tmp: /dev +-ssh_deletekeys: 0 ++ssh_deletekeys: 1 + ssh_genkeytypes: ~ + syslog_fix_perms: ~ + disable_vmware_customization: false +-- +1.8.3.1 + diff --git a/SOURCES/ci-Fix-for-network-configuration-not-persisting-after-r.patch b/SOURCES/ci-Fix-for-network-configuration-not-persisting-after-r.patch deleted file mode 100644 index 41e27ff..0000000 --- a/SOURCES/ci-Fix-for-network-configuration-not-persisting-after-r.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 4ab5a61c0f8378889d2a6b78710f49b0fb71d3c9 Mon Sep 17 00:00:00 2001 -From: Miroslav Rezanina -Date: Fri, 22 Nov 2019 07:46:38 +0100 -Subject: [PATCH 1/2] Fix for network configuration not persisting after reboot - -RH-Author: Eduardo Otubo -Message-id: <20190906121211.23172-1-otubo@redhat.com> -Patchwork-id: 90300 -O-Subject: [RHEL-7.8/RHEL-8.1.0 cloud-init PATCH] Fix for network configuration not persisting after reboot -Bugzilla: 1706482 -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Miroslav Rezanina -RH-Acked-by: Vitaly Kuznetsov - -The reasons the configuration does not persist after reboot includes -different aspects and they're all fixed on this patch: - - 1) The rpm package doesn't include the systemd-generator and -ds-identify. The systemd-generator is called early in the boot process -that calls ds-identify to check if there's any Data Source available in -the current boot. In the current use case, the Data Source is removed -from the VM on the second boot, this means cloud-init should disable -itself in order to keep the configuration it did in the first boot. - - 2) Even after adding those scripts, cloud-init was still being -executed and the configurations were being lost. The reason for this is -that the cloud-init systemd units had a wrong dependency - - WantedBy: multi-user.target - - Which would start them every time no matter the return of -ds-identify. The fix is to replace the dependency by the systemd unit to -cloud-init.target, which is the main cloud-init target enabled - or in -this case, disabled by ds-identify. The file cloud-init.target was also -missing on rpm package. - -After adding both scripts, the main cloud-init systemd target and -adjusting the systemd dependencies the configuration persists after -reboots and shutdowns. - -Signed-off-by: Eduardo Otubo ---- - redhat/cloud-init.spec.template | 15 +++++++++++++++ - rhel/systemd/cloud-config.service | 2 +- - rhel/systemd/cloud-final.service | 2 +- - rhel/systemd/cloud-init-local.service | 2 +- - rhel/systemd/cloud-init.service | 2 +- - rhel/systemd/cloud-init.target | 7 +++++++ - 6 files changed, 26 insertions(+), 4 deletions(-) - create mode 100644 rhel/systemd/cloud-init.target - -diff --git a/rhel/systemd/cloud-config.service b/rhel/systemd/cloud-config.service -index 12ca9df..f3dcd4b 100644 ---- a/rhel/systemd/cloud-config.service -+++ b/rhel/systemd/cloud-config.service -@@ -15,4 +15,4 @@ TimeoutSec=0 - StandardOutput=journal+console - - [Install] --WantedBy=multi-user.target -+WantedBy=cloud-init.target -diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service -index 32a83d8..739b7e3 100644 ---- a/rhel/systemd/cloud-final.service -+++ b/rhel/systemd/cloud-final.service -@@ -16,4 +16,4 @@ KillMode=process - StandardOutput=journal+console - - [Install] --WantedBy=multi-user.target -+WantedBy=cloud-init.target -diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service -index 656eddb..8f9f6c9 100644 ---- a/rhel/systemd/cloud-init-local.service -+++ b/rhel/systemd/cloud-init-local.service -@@ -28,4 +28,4 @@ TimeoutSec=0 - StandardOutput=journal+console - - [Install] --WantedBy=multi-user.target -+WantedBy=cloud-init.target -diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service -index 68fc5f1..d0023a0 100644 ---- a/rhel/systemd/cloud-init.service -+++ b/rhel/systemd/cloud-init.service -@@ -22,4 +22,4 @@ TimeoutSec=0 - StandardOutput=journal+console - - [Install] --WantedBy=multi-user.target -+WantedBy=cloud-init.target -diff --git a/rhel/systemd/cloud-init.target b/rhel/systemd/cloud-init.target -new file mode 100644 -index 0000000..083c3b6 ---- /dev/null -+++ b/rhel/systemd/cloud-init.target -@@ -0,0 +1,7 @@ -+# cloud-init target is enabled by cloud-init-generator -+# To disable it you can either: -+# a.) boot with kernel cmdline of 'cloud-init=disabled' -+# b.) touch a file /etc/cloud/cloud-init.disabled -+[Unit] -+Description=Cloud-init target -+After=multi-user.target --- -1.8.3.1 - diff --git a/SOURCES/ci-Make-cloud-init.service-execute-after-network-is-up.patch b/SOURCES/ci-Make-cloud-init.service-execute-after-network-is-up.patch index 34757c5..f9d9b73 100644 --- a/SOURCES/ci-Make-cloud-init.service-execute-after-network-is-up.patch +++ b/SOURCES/ci-Make-cloud-init.service-execute-after-network-is-up.patch @@ -1,25 +1,23 @@ -From 3114e706818b0eebb3c41b372a3515cf5ead275e Mon Sep 17 00:00:00 2001 +From dd95ebb925d4f74ffdcae50bb5eabc715d893cb2 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo -Date: Tue, 21 Apr 2020 16:58:28 +0200 +Date: Mon, 1 Jun 2020 13:59:06 +0200 Subject: [PATCH] Make cloud-init.service execute after network is up RH-Author: Eduardo Otubo -Message-id: <20200421122128.17489-1-otubo@redhat.com> -Patchwork-id: 96030 -O-Subject: [RHEL-8.2.0.z cloud-init PATCH] Make cloud-init.service execute after network is up -Bugzilla: 1826262 +Message-id: <20200526090804.2047-1-otubo@redhat.com> +Patchwork-id: 96809 +O-Subject: [RHEL-8.2.1 cloud-init PATCH] Make cloud-init.service execute after network is up +Bugzilla: 1831646 RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Daniel P. Berrange -RH-Acked-by: Cathy Avery - -BZ: 1826262 -BRANCH: rhel820/master-18.5 -BREW: 28038801 +RH-Acked-by: Miroslav Rezanina cloud-init.service needs to wait until network is fully up before continuing executing and configuring its service. Signed-off-by: Eduardo Otubo + +x-downstream-only: yes +Resolves: rhbz#1831646 Signed-off-by: Miroslav Rezanina --- rhel/systemd/cloud-init.service | 1 + diff --git a/SOURCES/ci-Remove-race-condition-between-cloud-init-and-Network.patch b/SOURCES/ci-Remove-race-condition-between-cloud-init-and-Network.patch deleted file mode 100644 index a46559b..0000000 --- a/SOURCES/ci-Remove-race-condition-between-cloud-init-and-Network.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 312666820cb51a738d8112181c331eb2ce109732 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 2 Mar 2020 10:46:35 +0100 -Subject: [PATCH] Remove race condition between cloud-init and NetworkManager - -Message-id: <20200302104635.11648-1-otubo@redhat.com> -Patchwork-id: 94098 -O-Subject: [RHEL-7.9/RHEL-8.2.0 cloud-init PATCH] Remove race condition between cloud-init and NetworkManager -Bugzilla: 1807797 -RH-Acked-by: Cathy Avery -RH-Acked-by: Mohammed Gamal - -BZ: 1748015 -BRANCH: rhel7/master-18.5 -BREW: 26924611 - -BZ: 1807797 -BRANCH: rhel820/master-18.5 -BREW: 26924957 - -cloud-init service is set to start before NetworkManager service starts, -but this does not avoid a race condition between them. NetworkManager -starts before cloud-init can write `dns=none' to the file: -/etc/NetworkManager/conf.d/99-cloud-init.conf. This way NetworkManager -doesn't read the configuration and erases all resolv.conf values upon -shutdown. On the next reboot neither cloud-init or NetworkManager will -write anything to resolv.conf, leaving it blank. - -This patch introduces a NM reload (try-restart) at the end of cloud-init -start up so it won't erase resolv.conf upon first shutdown. - -x-downstream-only: yes -resolves: rhbz#1748015, rhbz#1807797 and rhbz#1804780 - -Signed-off-by: Eduardo Otubo otubo@redhat.com -Signed-off-by: Miroslav Rezanina ---- - rhel/systemd/cloud-final.service | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service -index 739b7e3..f303483 100644 ---- a/rhel/systemd/cloud-final.service -+++ b/rhel/systemd/cloud-final.service -@@ -11,6 +11,8 @@ ExecStart=/usr/bin/cloud-init modules --mode=final - RemainAfterExit=yes - TimeoutSec=0 - KillMode=process -+ExecStartPost=/bin/echo "try restart NetworkManager.service" -+ExecStartPost=/usr/bin/systemctl try-restart NetworkManager.service - - # Output needs to appear in instance console output - StandardOutput=journal+console --- -1.8.3.1 - diff --git a/SOURCES/ci-Removing-cloud-user-from-wheel.patch b/SOURCES/ci-Removing-cloud-user-from-wheel.patch deleted file mode 100644 index b6a1b4d..0000000 --- a/SOURCES/ci-Removing-cloud-user-from-wheel.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 84cf125e1704034ccc6c379aced2924f9d0d30e6 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Thu, 23 Jan 2020 11:13:17 +0100 -Subject: [PATCH] Removing cloud-user from wheel - -RH-Author: Eduardo Otubo -Message-id: <20200123111317.15542-1-otubo@redhat.com> -Patchwork-id: 93442 -O-Subject: [RHEL-7.9/RHEL-8 cloud-init PATCH] Removing cloud-user from wheel -Bugzilla: 1785648 -RH-Acked-by: Stefan Hajnoczi -RH-Acked-by: Vitaly Kuznetsov - -BZ: 1549638 -BRANCH: rhel7/master-18.5 -UPSTREAM: -BREW: 25963032 - -BZ: 1785648 -BRANCH: rhel8/master-18.5 -UPSTREAM: -BREW: 25962773 - -Including cloud-user to both sudoers and wheel group makes the command -`sudo -v' to ask for password. Besides the bogus behavior, it's -unecessary to have the user on both. Removing the user from `wheel' -group solves the issue - -X-downstream-only: yes -Resolves: rhbz#1549638 -Resolves: rhbz#1785648 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - rhel/cloud.cfg | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg -index f0db3c1..82e8bf6 100644 ---- a/rhel/cloud.cfg -+++ b/rhel/cloud.cfg -@@ -57,7 +57,7 @@ system_info: - name: cloud-user - lock_passwd: true - gecos: Cloud User -- groups: [wheel, adm, systemd-journal] -+ groups: [adm, systemd-journal] - sudo: ["ALL=(ALL) NOPASSWD:ALL"] - shell: /bin/bash - distro: rhel --- -1.8.3.1 - diff --git a/SOURCES/ci-Revert-azure-ensure-that-networkmanager-hook-script-.patch b/SOURCES/ci-Revert-azure-ensure-that-networkmanager-hook-script-.patch deleted file mode 100644 index 5f1e0ef..0000000 --- a/SOURCES/ci-Revert-azure-ensure-that-networkmanager-hook-script-.patch +++ /dev/null @@ -1,86 +0,0 @@ -From a00bafbfc54b010d7bbe95536929c678288d4c80 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 1 Jul 2019 11:22:52 +0200 -Subject: [PATCH 1/2] Revert: azure: ensure that networkmanager hook script - runs - -RH-Author: Eduardo Otubo -Message-id: <20190701112252.32674-1-otubo@redhat.com> -Patchwork-id: 89162 -O-Subject: [rhel-8.1.0 cloud-init PATCH] Revert: azure: ensure that networkmanager hook script runs -Bugzilla: 1692914 -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Miroslav Rezanina - -This patch reverts the commit: - -commit c48497435e8195dbd87262c2f00e484e63fe3343 -Author: Lars Kellogg-Stedman -Date: Thu Jun 15 12:20:39 2017 -0400 - - azure: ensure that networkmanager hook script runs - - The networkmanager hook script was failing to run due to the changes - we made to resolve rhbz#1440831. This corrects the regression by - allowing the NM hook script to run regardless of whether or not - cloud-init is "enabled". - - Resolves: rhbz#1460206 - X-downstream-only: true - -Resolves: rhbz#1692914 -X-downstream-only: true - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - tools/hook-dhclient | 3 ++- - tools/hook-network-manager | 3 ++- - tools/hook-rhel.sh | 3 ++- - 3 files changed, 6 insertions(+), 3 deletions(-) - -diff --git a/tools/hook-dhclient b/tools/hook-dhclient -index 181cd51..02122f3 100755 ---- a/tools/hook-dhclient -+++ b/tools/hook-dhclient -@@ -13,7 +13,8 @@ is_azure() { - } - - is_enabled() { -- # only execute hooks if cloud-init is running on azure -+ # only execute hooks if cloud-init is enabled and on azure -+ [ -e /run/cloud-init/enabled ] || return 1 - is_azure - } - -diff --git a/tools/hook-network-manager b/tools/hook-network-manager -index 1d52cad..67d9044 100755 ---- a/tools/hook-network-manager -+++ b/tools/hook-network-manager -@@ -13,7 +13,8 @@ is_azure() { - } - - is_enabled() { -- # only execute hooks if cloud-init running on azure -+ # only execute hooks if cloud-init is enabled and on azure -+ [ -e /run/cloud-init/enabled ] || return 1 - is_azure - } - -diff --git a/tools/hook-rhel.sh b/tools/hook-rhel.sh -index d75767e..513a551 100755 ---- a/tools/hook-rhel.sh -+++ b/tools/hook-rhel.sh -@@ -13,7 +13,8 @@ is_azure() { - } - - is_enabled() { -- # only execute hooks if cloud-init is running on azure -+ # only execute hooks if cloud-init is enabled and on azure -+ [ -e /run/cloud-init/enabled ] || return 1 - is_azure - } - --- -1.8.3.1 - diff --git a/SOURCES/ci-azure-avoid-re-running-cloud-init-when-instance-id-i.patch b/SOURCES/ci-azure-avoid-re-running-cloud-init-when-instance-id-i.patch deleted file mode 100644 index a902a13..0000000 --- a/SOURCES/ci-azure-avoid-re-running-cloud-init-when-instance-id-i.patch +++ /dev/null @@ -1,225 +0,0 @@ -From ff70ab92aa97b3d43dbde34ef9f70b9ee44e197e Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 19 Feb 2020 10:30:02 +0100 -Subject: [PATCH 1/3] azure: avoid re-running cloud-init when instance-id is - byte-swapped (#84) - -RH-Author: Eduardo Otubo -Message-id: <20200219103002.24735-1-otubo@redhat.com> -Patchwork-id: 93933 -O-Subject: [RHEL-8.2.0/RHEL-7.9 cloud-init PATCH] azure: avoid re-running cloud-init when instance-id is byte-swapped (#84) -Bugzilla: 1788684 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Mohammed Gamal - -BZ: 1788684 -BRANCH: rhel820/master-18.5 -UPSTREAM: 129b1c4e -BREW: 26606326 - -BZ: 1788340 -BRANCH: rhel7/master-18.5 -UPSTREAM: 129b1c4e -BREW: 26606322 - -commit 129b1c4ea250619bd7caed7aaffacc796b0139f2 -Author: AOhassan <37305877+AOhassan@users.noreply.github.com> -Date: Thu Dec 12 13:51:42 2019 -0800 - - azure: avoid re-running cloud-init when instance-id is byte-swapped (#84) - - Azure stores the instance ID with an incorrect byte ordering for the - first three hyphen delimited parts. This results in invalid - is_new_instance checks forcing Azure datasource to recrawl the metadata - service. - - When persisting instance-id from the metadata service, swap the - instance-id string byte order such that it is consistent with - that returned by dmi information. Check whether the instance-id - string is a byte-swapped match when determining correctly whether - the Azure platform instance-id has actually changed. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/sources/DataSourceAzure.py | 16 ++++++++++--- - cloudinit/sources/helpers/azure.py | 27 ++++++++++++++++++++++ - tests/unittests/test_datasource/test_azure.py | 24 ++++++++++++++++--- - .../unittests/test_datasource/test_azure_helper.py | 19 +++++++++++++++ - 4 files changed, 80 insertions(+), 6 deletions(-) - -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -index 5baf8da..66bbe5e 100755 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -28,7 +28,8 @@ from cloudinit.reporting import events - - from cloudinit.sources.helpers.azure import (azure_ds_reporter, - azure_ds_telemetry_reporter, -- get_metadata_from_fabric) -+ get_metadata_from_fabric, -+ is_byte_swapped) - - LOG = logging.getLogger(__name__) - -@@ -458,8 +459,7 @@ class DataSourceAzure(sources.DataSource): - seed = _get_random_seed() - if seed: - crawled_data['metadata']['random_seed'] = seed -- crawled_data['metadata']['instance-id'] = util.read_dmi_data( -- 'system-uuid') -+ crawled_data['metadata']['instance-id'] = self._iid() - - if perform_reprovision: - LOG.info("Reporting ready to Azure after getting ReprovisionData") -@@ -530,6 +530,16 @@ class DataSourceAzure(sources.DataSource): - # quickly (local check only) if self.instance_id is still valid - return sources.instance_id_matches_system_uuid(self.get_instance_id()) - -+ def _iid(self, previous=None): -+ prev_iid_path = os.path.join( -+ self.paths.get_cpath('data'), 'instance-id') -+ iid = util.read_dmi_data('system-uuid') -+ if os.path.exists(prev_iid_path): -+ previous = util.load_file(prev_iid_path).strip() -+ if is_byte_swapped(previous, iid): -+ return previous -+ return iid -+ - @azure_ds_telemetry_reporter - def setup(self, is_new_instance): - if self._negotiated is False: -diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py -index 82c4c8c..c2a57cc 100755 ---- a/cloudinit/sources/helpers/azure.py -+++ b/cloudinit/sources/helpers/azure.py -@@ -7,6 +7,7 @@ import re - import socket - import struct - import time -+import textwrap - - from cloudinit.net import dhcp - from cloudinit import stages -@@ -40,6 +41,32 @@ def azure_ds_telemetry_reporter(func): - return impl - - -+def is_byte_swapped(previous_id, current_id): -+ """ -+ Azure stores the instance ID with an incorrect byte ordering for the -+ first parts. This corrects the byte order such that it is consistent with -+ that returned by the metadata service. -+ """ -+ if previous_id == current_id: -+ return False -+ -+ def swap_bytestring(s, width=2): -+ dd = [byte for byte in textwrap.wrap(s, 2)] -+ dd.reverse() -+ return ''.join(dd) -+ -+ parts = current_id.split('-') -+ swapped_id = '-'.join([ -+ swap_bytestring(parts[0]), -+ swap_bytestring(parts[1]), -+ swap_bytestring(parts[2]), -+ parts[3], -+ parts[4] -+ ]) -+ -+ return previous_id == swapped_id -+ -+ - @contextmanager - def cd(newdir): - prevdir = os.getcwd() -diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py -index bc8b42c..1fb0565 100644 ---- a/tests/unittests/test_datasource/test_azure.py -+++ b/tests/unittests/test_datasource/test_azure.py -@@ -314,7 +314,7 @@ scbus-1 on xpt0 bus 0 - 'public-keys': [], - }) - -- self.instance_id = 'test-instance-id' -+ self.instance_id = 'D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8' - - def _dmi_mocks(key): - if key == 'system-uuid': -@@ -511,7 +511,7 @@ fdescfs /dev/fd fdescfs rw 0 0 - 'subnet': [{'address': '10.0.0.0', 'prefix': '24'}]}, - 'ipv6': {'ipAddress': []}, - 'macAddress': '000D3A047598'}]}}, -- 'instance-id': 'test-instance-id', -+ 'instance-id': 'D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8', - 'local-hostname': u'myhost', - 'random_seed': 'wild'} - -@@ -881,6 +881,24 @@ fdescfs /dev/fd fdescfs rw 0 0 - self.assertTrue(ret) - self.assertEqual('value', dsrc.metadata['test']) - -+ def test_instance_id_endianness(self): -+ """Return the previous iid when dmi uuid is the byteswapped iid.""" -+ ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) -+ # byte-swapped previous -+ write_file( -+ os.path.join(self.paths.cloud_dir, 'data', 'instance-id'), -+ '544CDFD0-CB4E-4B4A-9954-5BDF3ED5C3B8') -+ ds.get_data() -+ self.assertEqual( -+ '544CDFD0-CB4E-4B4A-9954-5BDF3ED5C3B8', ds.metadata['instance-id']) -+ # not byte-swapped previous -+ write_file( -+ os.path.join(self.paths.cloud_dir, 'data', 'instance-id'), -+ '644CDFD0-CB4E-4B4A-9954-5BDF3ED5C3B8') -+ ds.get_data() -+ self.assertEqual( -+ 'D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8', ds.metadata['instance-id']) -+ - def test_instance_id_from_dmidecode_used(self): - ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) - ds.get_data() -@@ -1080,7 +1098,7 @@ class TestAzureBounce(CiTestCase): - - def _dmi_mocks(key): - if key == 'system-uuid': -- return 'test-instance-id' -+ return 'D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8' - elif key == 'chassis-asset-tag': - return '7783-7084-3265-9085-8269-3286-77' - raise RuntimeError('should not get here') -diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py -index bd006ab..7ad5cc1 100644 ---- a/tests/unittests/test_datasource/test_azure_helper.py -+++ b/tests/unittests/test_datasource/test_azure_helper.py -@@ -170,6 +170,25 @@ class TestGoalStateParsing(CiTestCase): - goal_state = self._get_goal_state(instance_id=instance_id) - self.assertEqual(instance_id, goal_state.instance_id) - -+ def test_instance_id_byte_swap(self): -+ """Return true when previous_iid is byteswapped current_iid""" -+ previous_iid = "D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8" -+ current_iid = "544CDFD0-CB4E-4B4A-9954-5BDF3ED5C3B8" -+ self.assertTrue( -+ azure_helper.is_byte_swapped(previous_iid, current_iid)) -+ -+ def test_instance_id_no_byte_swap_same_instance_id(self): -+ previous_iid = "D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8" -+ current_iid = "D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8" -+ self.assertFalse( -+ azure_helper.is_byte_swapped(previous_iid, current_iid)) -+ -+ def test_instance_id_no_byte_swap_diff_instance_id(self): -+ previous_iid = "D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8" -+ current_iid = "G0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8" -+ self.assertFalse( -+ azure_helper.is_byte_swapped(previous_iid, current_iid)) -+ - def test_certificates_xml_parsed_and_fetched_correctly(self): - http_client = mock.MagicMock() - certificates_url = 'TestCertificatesUrl' --- -1.8.3.1 - diff --git a/SOURCES/ci-azure-net-generate_fallback_nic-emits-network-v2-con.patch b/SOURCES/ci-azure-net-generate_fallback_nic-emits-network-v2-con.patch deleted file mode 100644 index 44293d6..0000000 --- a/SOURCES/ci-azure-net-generate_fallback_nic-emits-network-v2-con.patch +++ /dev/null @@ -1,374 +0,0 @@ -From b7b814bc0f4e7b63b50106d292e91f2c9555ad87 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 4 May 2020 12:40:00 +0200 -Subject: [PATCH 3/6] azure/net: generate_fallback_nic emits network v2 config - instead of v1 - -RH-Author: Eduardo Otubo -Message-id: <20200327152826.13343-4-otubo@redhat.com> -Patchwork-id: 94460 -O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 3/6] azure/net: generate_fallback_nic emits network v2 config instead of v1 -Bugzilla: 1811753 -RH-Acked-by: Cathy Avery -RH-Acked-by: Vitaly Kuznetsov - -commit 7f674256c1426ffc419fd6b13e66a58754d94939 -Author: Chad Smith -Date: Tue Aug 13 20:13:05 2019 +0000 - - azure/net: generate_fallback_nic emits network v2 config instead of v1 - - The function generate_fallback_config is used by Azure by default when - not consuming IMDS configuration data. This function is also used by any - datasource which does not implement it's own network config. This simple - fallback configuration sets up dhcp on the most likely NIC. It will now - emit network v2 instead of network v1. - - This is a step toward moving all components talking in v2 and allows us - to avoid costly conversions between v1 and v2 for newer distributions - which rely on netplan. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/__init__.py | 31 +++++--------- - cloudinit/net/network_state.py | 12 ++++-- - cloudinit/net/tests/test_init.py | 19 +++++---- - cloudinit/sources/DataSourceAzure.py | 7 +++- - tests/unittests/test_datasource/test_azure.py | 59 ++++++++++++++++++++++++++- - tests/unittests/test_net.py | 41 +++++++++++++++++-- - 6 files changed, 130 insertions(+), 39 deletions(-) - -diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py -index 41659b1..b26a190 100644 ---- a/cloudinit/net/__init__.py -+++ b/cloudinit/net/__init__.py -@@ -268,32 +268,23 @@ def find_fallback_nic(blacklist_drivers=None): - - - def generate_fallback_config(blacklist_drivers=None, config_driver=None): -- """Determine which attached net dev is most likely to have a connection and -- generate network state to run dhcp on that interface""" -- -+ """Generate network cfg v2 for dhcp on the NIC most likely connected.""" - if not config_driver: - config_driver = False - - target_name = find_fallback_nic(blacklist_drivers=blacklist_drivers) -- if target_name: -- target_mac = read_sys_net_safe(target_name, 'address') -- nconf = {'config': [], 'version': 1} -- cfg = {'type': 'physical', 'name': target_name, -- 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]} -- # inject the device driver name, dev_id into config if enabled and -- # device has a valid device driver value -- if config_driver: -- driver = device_driver(target_name) -- if driver: -- cfg['params'] = { -- 'driver': driver, -- 'device_id': device_devid(target_name), -- } -- nconf['config'].append(cfg) -- return nconf -- else: -+ if not target_name: - # can't read any interfaces addresses (or there are none); give up - return None -+ target_mac = read_sys_net_safe(target_name, 'address') -+ cfg = {'dhcp4': True, 'set-name': target_name, -+ 'match': {'macaddress': target_mac.lower()}} -+ if config_driver: -+ driver = device_driver(target_name) -+ if driver: -+ cfg['match']['driver'] = driver -+ nconf = {'ethernets': {target_name: cfg}, 'version': 2} -+ return nconf - - - def apply_network_config_names(netcfg, strict_present=True, strict_busy=True): -diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py -index 4d19f56..71d3c0f 100644 ---- a/cloudinit/net/network_state.py -+++ b/cloudinit/net/network_state.py -@@ -596,6 +596,7 @@ class NetworkStateInterpreter(object): - eno1: - match: - macaddress: 00:11:22:33:44:55 -+ driver: hv_netsvc - wakeonlan: true - dhcp4: true - dhcp6: false -@@ -631,15 +632,18 @@ class NetworkStateInterpreter(object): - 'type': 'physical', - 'name': cfg.get('set-name', eth), - } -- mac_address = cfg.get('match', {}).get('macaddress', None) -+ match = cfg.get('match', {}) -+ mac_address = match.get('macaddress', None) - if not mac_address: - LOG.debug('NetworkState Version2: missing "macaddress" info ' - 'in config entry: %s: %s', eth, str(cfg)) -- phy_cmd.update({'mac_address': mac_address}) -- -+ phy_cmd['mac_address'] = mac_address -+ driver = match.get('driver', None) -+ if driver: -+ phy_cmd['params'] = {'driver': driver} - for key in ['mtu', 'match', 'wakeonlan']: - if key in cfg: -- phy_cmd.update({key: cfg.get(key)}) -+ phy_cmd[key] = cfg[key] - - subnets = self._v2_to_v1_ipcfg(cfg) - if len(subnets) > 0: -diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py -index 5519867..5349176 100644 ---- a/cloudinit/net/tests/test_init.py -+++ b/cloudinit/net/tests/test_init.py -@@ -218,9 +218,9 @@ class TestGenerateFallbackConfig(CiTestCase): - mac = 'aa:bb:cc:aa:bb:cc' - write_file(os.path.join(self.sysdir, 'eth1', 'address'), mac) - expected = { -- 'config': [{'type': 'physical', 'mac_address': mac, -- 'name': 'eth1', 'subnets': [{'type': 'dhcp'}]}], -- 'version': 1} -+ 'ethernets': {'eth1': {'match': {'macaddress': mac}, -+ 'dhcp4': True, 'set-name': 'eth1'}}, -+ 'version': 2} - self.assertEqual(expected, net.generate_fallback_config()) - - def test_generate_fallback_finds_dormant_eth_with_mac(self): -@@ -229,9 +229,9 @@ class TestGenerateFallbackConfig(CiTestCase): - mac = 'aa:bb:cc:aa:bb:cc' - write_file(os.path.join(self.sysdir, 'eth0', 'address'), mac) - expected = { -- 'config': [{'type': 'physical', 'mac_address': mac, -- 'name': 'eth0', 'subnets': [{'type': 'dhcp'}]}], -- 'version': 1} -+ 'ethernets': {'eth0': {'match': {'macaddress': mac}, 'dhcp4': True, -+ 'set-name': 'eth0'}}, -+ 'version': 2} - self.assertEqual(expected, net.generate_fallback_config()) - - def test_generate_fallback_finds_eth_by_operstate(self): -@@ -239,9 +239,10 @@ class TestGenerateFallbackConfig(CiTestCase): - mac = 'aa:bb:cc:aa:bb:cc' - write_file(os.path.join(self.sysdir, 'eth0', 'address'), mac) - expected = { -- 'config': [{'type': 'physical', 'mac_address': mac, -- 'name': 'eth0', 'subnets': [{'type': 'dhcp'}]}], -- 'version': 1} -+ 'ethernets': { -+ 'eth0': {'dhcp4': True, 'match': {'macaddress': mac}, -+ 'set-name': 'eth0'}}, -+ 'version': 2} - valid_operstates = ['dormant', 'down', 'lowerlayerdown', 'unknown'] - for state in valid_operstates: - write_file(os.path.join(self.sysdir, 'eth0', 'operstate'), state) -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -index 66bbe5e..8cb7e5c 100755 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -1242,7 +1242,7 @@ def parse_network_config(imds_metadata): - privateIpv4 = addr4['privateIpAddress'] - if privateIpv4: - if dev_config.get('dhcp4', False): -- # Append static address config for nic > 1 -+ # Append static address config for ip > 1 - netPrefix = intf['ipv4']['subnet'][0].get( - 'prefix', '24') - if not dev_config.get('addresses'): -@@ -1252,6 +1252,11 @@ def parse_network_config(imds_metadata): - ip=privateIpv4, prefix=netPrefix)) - else: - dev_config['dhcp4'] = True -+ # non-primary interfaces should have a higher -+ # route-metric (cost) so default routes prefer -+ # primary nic due to lower route-metric value -+ dev_config['dhcp4-overrides'] = { -+ 'route-metric': (idx + 1) * 100} - for addr6 in intf['ipv6']['ipAddress']: - privateIpv6 = addr6['privateIpAddress'] - if privateIpv6: -diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py -index 1fb0565..f2ff967 100644 ---- a/tests/unittests/test_datasource/test_azure.py -+++ b/tests/unittests/test_datasource/test_azure.py -@@ -13,6 +13,7 @@ from cloudinit.tests.helpers import ( - HttprettyTestCase, CiTestCase, populate_dir, mock, wrap_and_call, - ExitStack, PY26, SkipTest, resourceLocation) - -+import copy - import crypt - import httpretty - import json -@@ -111,6 +112,26 @@ NETWORK_METADATA = { - } - } - -+SECONDARY_INTERFACE = { -+ "macAddress": "220D3A047598", -+ "ipv6": { -+ "ipAddress": [] -+ }, -+ "ipv4": { -+ "subnet": [ -+ { -+ "prefix": "24", -+ "address": "10.0.1.0" -+ } -+ ], -+ "ipAddress": [ -+ { -+ "privateIpAddress": "10.0.1.5", -+ } -+ ] -+ } -+} -+ - MOCKPATH = 'cloudinit.sources.DataSourceAzure.' - - -@@ -632,8 +653,43 @@ fdescfs /dev/fd fdescfs rw 0 0 - 'ethernets': { - 'eth0': {'set-name': 'eth0', - 'match': {'macaddress': '00:0d:3a:04:75:98'}, -- 'dhcp4': True}}, -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 100}}}, -+ 'version': 2} -+ dsrc = self._get_ds(data) -+ dsrc.get_data() -+ self.assertEqual(expected_network_config, dsrc.network_config) -+ -+ def test_network_config_set_from_imds_route_metric_for_secondary_nic(self): -+ """Datasource.network_config adds route-metric to secondary nics.""" -+ sys_cfg = {'datasource': {'Azure': {'apply_network_config': True}}} -+ odata = {} -+ data = {'ovfcontent': construct_valid_ovf_env(data=odata), -+ 'sys_cfg': sys_cfg} -+ expected_network_config = { -+ 'ethernets': { -+ 'eth0': {'set-name': 'eth0', -+ 'match': {'macaddress': '00:0d:3a:04:75:98'}, -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 100}}, -+ 'eth1': {'set-name': 'eth1', -+ 'match': {'macaddress': '22:0d:3a:04:75:98'}, -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 200}}, -+ 'eth2': {'set-name': 'eth2', -+ 'match': {'macaddress': '33:0d:3a:04:75:98'}, -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 300}}}, - 'version': 2} -+ imds_data = copy.deepcopy(NETWORK_METADATA) -+ imds_data['network']['interface'].append(SECONDARY_INTERFACE) -+ third_intf = copy.deepcopy(SECONDARY_INTERFACE) -+ third_intf['macAddress'] = third_intf['macAddress'].replace('22', '33') -+ third_intf['ipv4']['subnet'][0]['address'] = '10.0.2.0' -+ third_intf['ipv4']['ipAddress'][0]['privateIpAddress'] = '10.0.2.6' -+ imds_data['network']['interface'].append(third_intf) -+ -+ self.m_get_metadata_from_imds.return_value = imds_data - dsrc = self._get_ds(data) - dsrc.get_data() - self.assertEqual(expected_network_config, dsrc.network_config) -@@ -936,6 +992,7 @@ fdescfs /dev/fd fdescfs rw 0 0 - expected_cfg = { - 'ethernets': { - 'eth0': {'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 100}, - 'match': {'macaddress': '00:0d:3a:04:75:98'}, - 'set-name': 'eth0'}}, - 'version': 2} -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index a975678..206de56 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -1651,7 +1651,7 @@ DEFAULT_DEV_ATTRS = { - "carrier": False, - "dormant": False, - "operstate": "down", -- "address": "07-1C-C6-75-A4-BE", -+ "address": "07-1c-c6-75-a4-be", - "device/driver": None, - "device/device": None, - "name_assign_type": "4", -@@ -1702,6 +1702,39 @@ class TestGenerateFallbackConfig(CiTestCase): - @mock.patch("cloudinit.net.sys_dev_path") - @mock.patch("cloudinit.net.read_sys_net") - @mock.patch("cloudinit.net.get_devicelist") -+ def test_device_driver_v2(self, mock_get_devicelist, mock_read_sys_net, -+ mock_sys_dev_path): -+ """Network configuration for generate_fallback_config is version 2.""" -+ devices = { -+ 'eth0': { -+ 'bridge': False, 'carrier': False, 'dormant': False, -+ 'operstate': 'down', 'address': '00:11:22:33:44:55', -+ 'device/driver': 'hv_netsvc', 'device/device': '0x3', -+ 'name_assign_type': '4'}, -+ 'eth1': { -+ 'bridge': False, 'carrier': False, 'dormant': False, -+ 'operstate': 'down', 'address': '00:11:22:33:44:55', -+ 'device/driver': 'mlx4_core', 'device/device': '0x7', -+ 'name_assign_type': '4'}, -+ -+ } -+ -+ tmp_dir = self.tmp_dir() -+ _setup_test(tmp_dir, mock_get_devicelist, -+ mock_read_sys_net, mock_sys_dev_path, -+ dev_attrs=devices) -+ -+ network_cfg = net.generate_fallback_config(config_driver=True) -+ expected = { -+ 'ethernets': {'eth0': {'dhcp4': True, 'set-name': 'eth0', -+ 'match': {'macaddress': '00:11:22:33:44:55', -+ 'driver': 'hv_netsvc'}}}, -+ 'version': 2} -+ self.assertEqual(expected, network_cfg) -+ -+ @mock.patch("cloudinit.net.sys_dev_path") -+ @mock.patch("cloudinit.net.read_sys_net") -+ @mock.patch("cloudinit.net.get_devicelist") - def test_device_driver(self, mock_get_devicelist, mock_read_sys_net, - mock_sys_dev_path): - devices = { -@@ -1981,7 +2014,7 @@ class TestRhelSysConfigRendering(CiTestCase): - # - BOOTPROTO=dhcp - DEVICE=eth1000 --HWADDR=07-1C-C6-75-A4-BE -+HWADDR=07-1c-c6-75-a4-be - ONBOOT=yes - TYPE=Ethernet - USERCTL=no -@@ -2354,7 +2387,7 @@ class TestOpenSuseSysConfigRendering(CiTestCase): - # - BOOTPROTO=dhcp - DEVICE=eth1000 --HWADDR=07-1C-C6-75-A4-BE -+HWADDR=07-1c-c6-75-a4-be - NM_CONTROLLED=no - ONBOOT=yes - TYPE=Ethernet -@@ -2728,13 +2761,13 @@ class TestNetplanNetRendering(CiTestCase): - - expected = """ - network: -- version: 2 - ethernets: - eth1000: - dhcp4: true - match: - macaddress: 07-1c-c6-75-a4-be - set-name: eth1000 -+ version: 2 - """ - self.assertEqual(expected.lstrip(), contents.lstrip()) - self.assertEqual(1, mock_clean_default.call_count) --- -1.8.3.1 - diff --git a/SOURCES/ci-azure-support-matching-dhcp-route-metrics-for-dual-s.patch b/SOURCES/ci-azure-support-matching-dhcp-route-metrics-for-dual-s.patch deleted file mode 100644 index f6114eb..0000000 --- a/SOURCES/ci-azure-support-matching-dhcp-route-metrics-for-dual-s.patch +++ /dev/null @@ -1,354 +0,0 @@ -From 1b1a179bab885c19a4793c4a6cc2b48a541b9a84 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 4 May 2020 12:40:04 +0200 -Subject: [PATCH 4/6] azure: support matching dhcp route-metrics for dual-stack - ipv4 ipv6 - -RH-Author: Eduardo Otubo -Message-id: <20200327152826.13343-5-otubo@redhat.com> -Patchwork-id: 94455 -O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 4/6] azure: support matching dhcp route-metrics for dual-stack ipv4 ipv6 -Bugzilla: 1811753 -RH-Acked-by: Cathy Avery -RH-Acked-by: Vitaly Kuznetsov - -commit 02f07b666adc62d70c4f1a98c2ae80cb6629fa9a -Author: Chad Smith -Date: Mon Nov 4 22:11:37 2019 +0000 - - azure: support matching dhcp route-metrics for dual-stack ipv4 ipv6 - - Network v2 configuration for Azure will set both dhcp4 and - dhcp6 to False by default. - - When IPv6 privateIpAddresses are present for an interface in Azure's - Instance Metadata Service (IMDS), set dhcp6: True and provide a - route-metric value that will match the corresponding dhcp4 route-metric. - The route-metric value will increase by 100 for each additional - interface present to ensure the primary interface has a route to IMDS. - - Also fix dhcp route-metric rendering for eni and sysconfig distros. - - LP: #1850308 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/network_state.py | 17 ++++- - cloudinit/net/sysconfig.py | 6 +- - cloudinit/sources/DataSourceAzure.py | 10 ++- - tests/unittests/test_datasource/test_azure.py | 101 ++++++++++++++++++++++++++ - tests/unittests/test_net.py | 54 ++++++++++++++ - 5 files changed, 178 insertions(+), 10 deletions(-) - -diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py -index 71d3c0f..571eb57 100644 ---- a/cloudinit/net/network_state.py -+++ b/cloudinit/net/network_state.py -@@ -21,8 +21,9 @@ NETWORK_STATE_REQUIRED_KEYS = { - 1: ['version', 'config', 'network_state'], - } - NETWORK_V2_KEY_FILTER = [ -- 'addresses', 'dhcp4', 'dhcp6', 'gateway4', 'gateway6', 'interfaces', -- 'match', 'mtu', 'nameservers', 'renderer', 'set-name', 'wakeonlan' -+ 'addresses', 'dhcp4', 'dhcp4-overrides', 'dhcp6', 'dhcp6-overrides', -+ 'gateway4', 'gateway6', 'interfaces', 'match', 'mtu', 'nameservers', -+ 'renderer', 'set-name', 'wakeonlan' - ] - - NET_CONFIG_TO_V2 = { -@@ -734,12 +735,20 @@ class NetworkStateInterpreter(object): - def _v2_to_v1_ipcfg(self, cfg): - """Common ipconfig extraction from v2 to v1 subnets array.""" - -+ def _add_dhcp_overrides(overrides, subnet): -+ if 'route-metric' in overrides: -+ subnet['metric'] = overrides['route-metric'] -+ - subnets = [] - if cfg.get('dhcp4'): -- subnets.append({'type': 'dhcp4'}) -+ subnet = {'type': 'dhcp4'} -+ _add_dhcp_overrides(cfg.get('dhcp4-overrides', {}), subnet) -+ subnets.append(subnet) - if cfg.get('dhcp6'): -+ subnet = {'type': 'dhcp6'} - self.use_ipv6 = True -- subnets.append({'type': 'dhcp6'}) -+ _add_dhcp_overrides(cfg.get('dhcp6-overrides', {}), subnet) -+ subnets.append(subnet) - - gateway4 = None - gateway6 = None -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index 5c1b4eb..a4c7660 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -373,6 +373,9 @@ class Renderer(renderer.Renderer): - ipv6_index = -1 - for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): - subnet_type = subnet.get('type') -+ # metric may apply to both dhcp and static config -+ if 'metric' in subnet: -+ iface_cfg['METRIC'] = subnet['metric'] - if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']: - if has_default_route and iface_cfg['BOOTPROTO'] != 'none': - iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False -@@ -401,9 +404,6 @@ class Renderer(renderer.Renderer): - else: - iface_cfg['GATEWAY'] = subnet['gateway'] - -- if 'metric' in subnet: -- iface_cfg['METRIC'] = subnet['metric'] -- - if 'dns_search' in subnet: - iface_cfg['DOMAIN'] = ' '.join(subnet['dns_search']) - -diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py -index 8cb7e5c..e4d0708 100755 ---- a/cloudinit/sources/DataSourceAzure.py -+++ b/cloudinit/sources/DataSourceAzure.py -@@ -1237,7 +1237,8 @@ def parse_network_config(imds_metadata): - network_metadata = imds_metadata['network'] - for idx, intf in enumerate(network_metadata['interface']): - nicname = 'eth{idx}'.format(idx=idx) -- dev_config = {} -+ dev_config = {'dhcp4': False, 'dhcp6': False} -+ dhcp_override = {'route-metric': (idx + 1) * 100} - for addr4 in intf['ipv4']['ipAddress']: - privateIpv4 = addr4['privateIpAddress'] - if privateIpv4: -@@ -1255,12 +1256,15 @@ def parse_network_config(imds_metadata): - # non-primary interfaces should have a higher - # route-metric (cost) so default routes prefer - # primary nic due to lower route-metric value -- dev_config['dhcp4-overrides'] = { -- 'route-metric': (idx + 1) * 100} -+ dev_config['dhcp4-overrides'] = dhcp_override - for addr6 in intf['ipv6']['ipAddress']: - privateIpv6 = addr6['privateIpAddress'] - if privateIpv6: - dev_config['dhcp6'] = True -+ # non-primary interfaces should have a higher -+ # route-metric (cost) so default routes prefer -+ # primary nic due to lower route-metric value -+ dev_config['dhcp6-overrides'] = dhcp_override - break - if dev_config: - mac = ':'.join(re.findall(r'..', intf['macAddress'])) -diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py -index f2ff967..8da3233 100644 ---- a/tests/unittests/test_datasource/test_azure.py -+++ b/tests/unittests/test_datasource/test_azure.py -@@ -135,6 +135,102 @@ SECONDARY_INTERFACE = { - MOCKPATH = 'cloudinit.sources.DataSourceAzure.' - - -+class TestParseNetworkConfig(CiTestCase): -+ -+ maxDiff = None -+ -+ def test_single_ipv4_nic_configuration(self): -+ """parse_network_config emits dhcp on single nic with ipv4""" -+ expected = {'ethernets': { -+ 'eth0': {'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 100}, -+ 'dhcp6': False, -+ 'match': {'macaddress': '00:0d:3a:04:75:98'}, -+ 'set-name': 'eth0'}}, 'version': 2} -+ self.assertEqual(expected, dsaz.parse_network_config(NETWORK_METADATA)) -+ -+ def test_increases_route_metric_for_non_primary_nics(self): -+ """parse_network_config increases route-metric for each nic""" -+ expected = {'ethernets': { -+ 'eth0': {'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 100}, -+ 'dhcp6': False, -+ 'match': {'macaddress': '00:0d:3a:04:75:98'}, -+ 'set-name': 'eth0'}, -+ 'eth1': {'set-name': 'eth1', -+ 'match': {'macaddress': '22:0d:3a:04:75:98'}, -+ 'dhcp6': False, -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 200}}, -+ 'eth2': {'set-name': 'eth2', -+ 'match': {'macaddress': '33:0d:3a:04:75:98'}, -+ 'dhcp6': False, -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 300}}}, 'version': 2} -+ imds_data = copy.deepcopy(NETWORK_METADATA) -+ imds_data['network']['interface'].append(SECONDARY_INTERFACE) -+ third_intf = copy.deepcopy(SECONDARY_INTERFACE) -+ third_intf['macAddress'] = third_intf['macAddress'].replace('22', '33') -+ third_intf['ipv4']['subnet'][0]['address'] = '10.0.2.0' -+ third_intf['ipv4']['ipAddress'][0]['privateIpAddress'] = '10.0.2.6' -+ imds_data['network']['interface'].append(third_intf) -+ self.assertEqual(expected, dsaz.parse_network_config(imds_data)) -+ -+ def test_ipv4_and_ipv6_route_metrics_match_for_nics(self): -+ """parse_network_config emits matching ipv4 and ipv6 route-metrics.""" -+ expected = {'ethernets': { -+ 'eth0': {'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 100}, -+ 'dhcp6': False, -+ 'match': {'macaddress': '00:0d:3a:04:75:98'}, -+ 'set-name': 'eth0'}, -+ 'eth1': {'set-name': 'eth1', -+ 'match': {'macaddress': '22:0d:3a:04:75:98'}, -+ 'dhcp4': True, -+ 'dhcp6': False, -+ 'dhcp4-overrides': {'route-metric': 200}}, -+ 'eth2': {'set-name': 'eth2', -+ 'match': {'macaddress': '33:0d:3a:04:75:98'}, -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 300}, -+ 'dhcp6': True, -+ 'dhcp6-overrides': {'route-metric': 300}}}, 'version': 2} -+ imds_data = copy.deepcopy(NETWORK_METADATA) -+ imds_data['network']['interface'].append(SECONDARY_INTERFACE) -+ third_intf = copy.deepcopy(SECONDARY_INTERFACE) -+ third_intf['macAddress'] = third_intf['macAddress'].replace('22', '33') -+ third_intf['ipv4']['subnet'][0]['address'] = '10.0.2.0' -+ third_intf['ipv4']['ipAddress'][0]['privateIpAddress'] = '10.0.2.6' -+ third_intf['ipv6'] = { -+ "subnet": [{"prefix": "64", "address": "2001:dead:beef::2"}], -+ "ipAddress": [{"privateIpAddress": "2001:dead:beef::1"}] -+ } -+ imds_data['network']['interface'].append(third_intf) -+ self.assertEqual(expected, dsaz.parse_network_config(imds_data)) -+ -+ def test_ipv4_secondary_ips_will_be_static_addrs(self): -+ """parse_network_config emits primary ipv4 as dhcp others are static""" -+ expected = {'ethernets': { -+ 'eth0': {'addresses': ['10.0.0.5/24'], -+ 'dhcp4': True, -+ 'dhcp4-overrides': {'route-metric': 100}, -+ 'dhcp6': True, -+ 'dhcp6-overrides': {'route-metric': 100}, -+ 'match': {'macaddress': '00:0d:3a:04:75:98'}, -+ 'set-name': 'eth0'}}, 'version': 2} -+ imds_data = copy.deepcopy(NETWORK_METADATA) -+ nic1 = imds_data['network']['interface'][0] -+ nic1['ipv4']['ipAddress'].append({'privateIpAddress': '10.0.0.5'}) -+ -+ # Secondary ipv6 addresses currently ignored/unconfigured -+ nic1['ipv6'] = { -+ "subnet": [{"prefix": "10", "address": "2001:dead:beef::16"}], -+ "ipAddress": [{"privateIpAddress": "2001:dead:beef::1"}, -+ {"privateIpAddress": "2001:dead:beef::2"}] -+ } -+ self.assertEqual(expected, dsaz.parse_network_config(imds_data)) -+ -+ - class TestGetMetadataFromIMDS(HttprettyTestCase): - - with_logs = True -@@ -653,6 +749,7 @@ fdescfs /dev/fd fdescfs rw 0 0 - 'ethernets': { - 'eth0': {'set-name': 'eth0', - 'match': {'macaddress': '00:0d:3a:04:75:98'}, -+ 'dhcp6': False, - 'dhcp4': True, - 'dhcp4-overrides': {'route-metric': 100}}}, - 'version': 2} -@@ -670,14 +767,17 @@ fdescfs /dev/fd fdescfs rw 0 0 - 'ethernets': { - 'eth0': {'set-name': 'eth0', - 'match': {'macaddress': '00:0d:3a:04:75:98'}, -+ 'dhcp6': False, - 'dhcp4': True, - 'dhcp4-overrides': {'route-metric': 100}}, - 'eth1': {'set-name': 'eth1', - 'match': {'macaddress': '22:0d:3a:04:75:98'}, -+ 'dhcp6': False, - 'dhcp4': True, - 'dhcp4-overrides': {'route-metric': 200}}, - 'eth2': {'set-name': 'eth2', - 'match': {'macaddress': '33:0d:3a:04:75:98'}, -+ 'dhcp6': False, - 'dhcp4': True, - 'dhcp4-overrides': {'route-metric': 300}}}, - 'version': 2} -@@ -993,6 +1093,7 @@ fdescfs /dev/fd fdescfs rw 0 0 - 'ethernets': { - 'eth0': {'dhcp4': True, - 'dhcp4-overrides': {'route-metric': 100}, -+ 'dhcp6': False, - 'match': {'macaddress': '00:0d:3a:04:75:98'}, - 'set-name': 'eth0'}}, - 'version': 2} -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 206de56..df5658d 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -2304,6 +2304,36 @@ USERCTL=no - self._compare_files_to_expected(entry['expected_sysconfig'], found) - self._assert_headers(found) - -+ def test_from_v2_route_metric(self): -+ """verify route-metric gets rendered on nic when source is netplan.""" -+ overrides = {'route-metric': 100} -+ v2base = { -+ 'version': 2, -+ 'ethernets': { -+ 'eno1': {'dhcp4': True, -+ 'match': {'macaddress': '07-1c-c6-75-a4-be'}}}} -+ expected = { -+ 'ifcfg-eno1': textwrap.dedent("""\ -+ BOOTPROTO=dhcp -+ DEVICE=eno1 -+ HWADDR=07-1c-c6-75-a4-be -+ METRIC=100 -+ NM_CONTROLLED=no -+ ONBOOT=yes -+ STARTMODE=auto -+ TYPE=Ethernet -+ USERCTL=no -+ """), -+ } -+ for dhcp_ver in ('dhcp4', 'dhcp6'): -+ v2data = copy.deepcopy(v2base) -+ if dhcp_ver == 'dhcp6': -+ expected['ifcfg-eno1'] += "IPV6INIT=yes\nDHCPV6C=yes\n" -+ v2data['ethernets']['eno1'].update( -+ {dhcp_ver: True, '{0}-overrides'.format(dhcp_ver): overrides}) -+ self._compare_files_to_expected( -+ expected, self._render_and_read(network_config=v2data)) -+ - - class TestOpenSuseSysConfigRendering(CiTestCase): - -@@ -2725,6 +2755,30 @@ iface eth0 inet dhcp - config = sysconfig.ConfigObj(nm_cfg) - self.assertIn('ifcfg-rh', config['main']['plugins']) - -+ def test_v2_route_metric_to_eni(self): -+ """Network v2 route-metric overrides are preserved in eni output""" -+ tmp_dir = self.tmp_dir() -+ renderer = eni.Renderer() -+ expected_tmpl = textwrap.dedent("""\ -+ auto lo -+ iface lo inet loopback -+ -+ auto eth0 -+ iface eth0 inet{suffix} dhcp -+ metric 100 -+ """) -+ for dhcp_ver in ('dhcp4', 'dhcp6'): -+ suffix = '6' if dhcp_ver == 'dhcp6' else '' -+ dhcp_cfg = { -+ dhcp_ver: True, -+ '{ver}-overrides'.format(ver=dhcp_ver): {'route-metric': 100}} -+ v2_input = {'version': 2, 'ethernets': {'eth0': dhcp_cfg}} -+ ns = network_state.parse_net_config_data(v2_input) -+ renderer.render_network_state(ns, target=tmp_dir) -+ self.assertEqual( -+ expected_tmpl.format(suffix=suffix), -+ dir2dict(tmp_dir)['/etc/network/interfaces']) -+ - - class TestNetplanNetRendering(CiTestCase): - --- -1.8.3.1 - diff --git a/SOURCES/ci-cc_mounts-check-if-mount-a-on-no-change-fstab-path.patch b/SOURCES/ci-cc_mounts-check-if-mount-a-on-no-change-fstab-path.patch deleted file mode 100644 index d0f1fe7..0000000 --- a/SOURCES/ci-cc_mounts-check-if-mount-a-on-no-change-fstab-path.patch +++ /dev/null @@ -1,129 +0,0 @@ -From 2604984fc44dde89dac847de9f95011713d448ff Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 29 May 2019 13:41:49 +0200 -Subject: [PATCH 5/5] cc_mounts: check if mount -a on no-change fstab path - -RH-Author: Eduardo Otubo -Message-id: <20190529134149.842-6-otubo@redhat.com> -Patchwork-id: 88269 -O-Subject: [RHEL-8.0.1/RHEL-8.1.0 cloud-init PATCHv2 5/5] cc_mounts: check if mount -a on no-change fstab path -Bugzilla: 1691986 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Cathy Avery - -From: "Jason Zions (MSFT)" -commit acc25d8d7d603313059ac35b4253b504efc560a9 -Author: Jason Zions (MSFT) -Date: Wed May 8 22:47:07 2019 +0000 - - cc_mounts: check if mount -a on no-change fstab path - - Under some circumstances, cc_disk_setup may reformat volumes which - already appear in /etc/fstab (e.g. Azure ephemeral drive is reformatted - from NTFS to ext4 after service-heal). Normally, cc_mounts only calls - mount -a if it altered /etc/fstab. With this change cc_mounts will read - /proc/mounts and verify if configured mounts are already mounted and if - not raise flag to request a mount -a. This handles the case where no - changes to fstab occur but a mount -a is required due to change in - underlying device which prevented the .mount unit from running until - after disk was reformatted. - - LP: #1825596 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/config/cc_mounts.py | 11 ++++++++ - .../unittests/test_handler/test_handler_mounts.py | 30 +++++++++++++++++++++- - 2 files changed, 40 insertions(+), 1 deletion(-) - -diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py -index 339baba..123ffb8 100644 ---- a/cloudinit/config/cc_mounts.py -+++ b/cloudinit/config/cc_mounts.py -@@ -439,6 +439,7 @@ def handle(_name, cfg, cloud, log, _args): - - cc_lines = [] - needswap = False -+ need_mount_all = False - dirs = [] - for line in actlist: - # write 'comment' in the fs_mntops, entry, claiming this -@@ -449,11 +450,18 @@ def handle(_name, cfg, cloud, log, _args): - dirs.append(line[1]) - cc_lines.append('\t'.join(line)) - -+ mount_points = [v['mountpoint'] for k, v in util.mounts().items() -+ if 'mountpoint' in v] - for d in dirs: - try: - util.ensure_dir(d) - except Exception: - util.logexc(log, "Failed to make '%s' config-mount", d) -+ # dirs is list of directories on which a volume should be mounted. -+ # If any of them does not already show up in the list of current -+ # mount points, we will definitely need to do mount -a. -+ if not need_mount_all and d not in mount_points: -+ need_mount_all = True - - sadds = [WS.sub(" ", n) for n in cc_lines] - sdrops = [WS.sub(" ", n) for n in fstab_removed] -@@ -473,6 +481,9 @@ def handle(_name, cfg, cloud, log, _args): - log.debug("No changes to /etc/fstab made.") - else: - log.debug("Changes to fstab: %s", sops) -+ need_mount_all = True -+ -+ if need_mount_all: - activate_cmds.append(["mount", "-a"]) - if uses_systemd: - activate_cmds.append(["systemctl", "daemon-reload"]) -diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py -index 8fea6c2..0fb160b 100644 ---- a/tests/unittests/test_handler/test_handler_mounts.py -+++ b/tests/unittests/test_handler/test_handler_mounts.py -@@ -154,7 +154,15 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase): - return_value=True) - - self.add_patch('cloudinit.config.cc_mounts.util.subp', -- 'mock_util_subp') -+ 'm_util_subp') -+ -+ self.add_patch('cloudinit.config.cc_mounts.util.mounts', -+ 'mock_util_mounts', -+ return_value={ -+ '/dev/sda1': {'fstype': 'ext4', -+ 'mountpoint': '/', -+ 'opts': 'rw,relatime,discard' -+ }}) - - self.mock_cloud = mock.Mock() - self.mock_log = mock.Mock() -@@ -230,4 +238,24 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase): - fstab_new_content = fd.read() - self.assertEqual(fstab_expected_content, fstab_new_content) - -+ def test_no_change_fstab_sets_needs_mount_all(self): -+ '''verify unchanged fstab entries are mounted if not call mount -a''' -+ fstab_original_content = ( -+ 'LABEL=cloudimg-rootfs / ext4 defaults 0 0\n' -+ 'LABEL=UEFI /boot/efi vfat defaults 0 0\n' -+ '/dev/vdb /mnt auto defaults,noexec,comment=cloudconfig 0 2\n' -+ ) -+ fstab_expected_content = fstab_original_content -+ cc = {'mounts': [ -+ ['/dev/vdb', '/mnt', 'auto', 'defaults,noexec']]} -+ with open(cc_mounts.FSTAB_PATH, 'w') as fd: -+ fd.write(fstab_original_content) -+ with open(cc_mounts.FSTAB_PATH, 'r') as fd: -+ fstab_new_content = fd.read() -+ self.assertEqual(fstab_expected_content, fstab_new_content) -+ cc_mounts.handle(None, cc, self.mock_cloud, self.mock_log, []) -+ self.m_util_subp.assert_has_calls([ -+ mock.call(['mount', '-a']), -+ mock.call(['systemctl', 'daemon-reload'])]) -+ - # vi: ts=4 expandtab --- -1.8.3.1 - diff --git a/SOURCES/ci-cloud-init.service.tmpl-use-rhel-instead-of-redhat-4.patch b/SOURCES/ci-cloud-init.service.tmpl-use-rhel-instead-of-redhat-4.patch new file mode 100644 index 0000000..f4ec784 --- /dev/null +++ b/SOURCES/ci-cloud-init.service.tmpl-use-rhel-instead-of-redhat-4.patch @@ -0,0 +1,46 @@ +From 344b8c150479547f48a30a8edab3717d00fb0fca Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Wed, 24 Jun 2020 07:55:03 +0200 +Subject: [PATCH 3/3] cloud-init.service.tmpl: use "rhel" instead of "redhat" + (#452) + +RH-Author: Eduardo Otubo +Message-id: <20200623154034.28563-4-otubo@redhat.com> +Patchwork-id: 97784 +O-Subject: [RHEL-8.3.0/RHEL-8.2.1 cloud-init PATCH 3/3] cloud-init.service.tmpl: use "rhel" instead of "redhat" (#452) +Bugzilla: 1844366 +RH-Acked-by: Cathy Avery +RH-Acked-by: Mohammed Gamal + +From: Daniel Watkins + +commit ddc4c2de1b1e716b31384af92f5356bfc6136944 +Author: Daniel Watkins +Date: Tue Jun 23 09:43:04 2020 -0400 + + cloud-init.service.tmpl: use "rhel" instead of "redhat" (#452) + + We use "rhel" consistently everywhere else. + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + systemd/cloud-init.service.tmpl | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/systemd/cloud-init.service.tmpl b/systemd/cloud-init.service.tmpl +index 9ad3574..af6d9a8 100644 +--- a/systemd/cloud-init.service.tmpl ++++ b/systemd/cloud-init.service.tmpl +@@ -10,7 +10,7 @@ After=systemd-networkd-wait-online.service + {% if variant in ["ubuntu", "unknown", "debian"] %} + After=networking.service + {% endif %} +-{% if variant in ["centos", "fedora", "redhat"] %} ++{% if variant in ["centos", "fedora", "rhel"] %} + After=network.service + After=NetworkManager.service + {% endif %} +-- +1.8.3.1 + diff --git a/SOURCES/ci-cmd-main.py-Fix-missing-modules-init-key-in-modes-di.patch b/SOURCES/ci-cmd-main.py-Fix-missing-modules-init-key-in-modes-di.patch deleted file mode 100644 index a8ed261..0000000 --- a/SOURCES/ci-cmd-main.py-Fix-missing-modules-init-key-in-modes-di.patch +++ /dev/null @@ -1,67 +0,0 @@ -From a3b9673481bbe2fa35986aa757fd5321c3a7a371 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 12 Feb 2020 13:08:38 +0100 -Subject: [PATCH] cmd:main.py: Fix missing 'modules-init' key in modes dict - -RH-Author: Eduardo Otubo -Message-id: <20200212130838.24592-1-otubo@redhat.com> -Patchwork-id: 93781 -O-Subject: [RHEL-7.9/RHEL-8.2.0 cloud-init PATCH] cmd:main.py: Fix missing 'modules-init' key in modes dict -Bugzilla: 1802140 -RH-Acked-by: Cathy Avery -RH-Acked-by: Mohammed Gamal - -BZ: 1673170 -BRANCH: rhel7/master-18.5 -UPSTREAM: bdd9c0ac -BREW: 26372362 - -BZ: 1802140 -BRANCH: rhel820/master-18.5 -UPSTREAM: bdd9c0ac -BREW: 26372415 - -commit 96557ca26a73c490aee74a811c3a44ef1ac11d84 -Author: Antonio Romito -Date: Tue Apr 9 14:54:23 2019 +0000 - - cmd:main.py: Fix missing 'modules-init' key in modes dict - - Cloud-init's main.py will fail when presented with a new - stage name 'modules-init' if upgrading an older cloud-init. - Fix this by initializing unknown stage names before accessing. - - LP: #1815109 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/cmd/main.py | 9 +++++---- - 1 file changed, 5 insertions(+), 4 deletions(-) - -diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py -index 933c019..a5446da 100644 ---- a/cloudinit/cmd/main.py -+++ b/cloudinit/cmd/main.py -@@ -632,13 +632,14 @@ def status_wrapper(name, args, data_d=None, link_d=None): - 'start': None, - 'finished': None, - } -+ - if status is None: - status = {'v1': {}} -- for m in modes: -- status['v1'][m] = nullstatus.copy() - status['v1']['datasource'] = None -- elif mode not in status['v1']: -- status['v1'][mode] = nullstatus.copy() -+ -+ for m in modes: -+ if m not in status['v1']: -+ status['v1'][m] = nullstatus.copy() - - v1 = status['v1'] - v1['stage'] = mode --- -1.8.3.1 - diff --git a/SOURCES/ci-ec2-Do-not-log-IMDSv2-token-values-instead-use-REDAC.patch b/SOURCES/ci-ec2-Do-not-log-IMDSv2-token-values-instead-use-REDAC.patch new file mode 100644 index 0000000..bff4133 --- /dev/null +++ b/SOURCES/ci-ec2-Do-not-log-IMDSv2-token-values-instead-use-REDAC.patch @@ -0,0 +1,346 @@ +From 5e15faadff409327a8d47db1f23910e7174a20a4 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Fri, 26 Jun 2020 09:51:05 +0200 +Subject: [PATCH 1/2] ec2: Do not log IMDSv2 token values, instead use REDACTED + (#219) + +RH-Author: Eduardo Otubo +Message-id: <20200624114403.2164-2-otubo@redhat.com> +Patchwork-id: 97796 +O-Subject: [RHEL-8.2.1 cloud-init PATCH 1/2] ec2: Do not log IMDSv2 token values, instead use REDACTED (#219) +Bugzilla: 1850456 +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Mohammed Gamal + +commit 87cd040ed8fe7195cbb357ed3bbf53cd2a81436c +Author: Ryan Harper +Date: Wed Feb 19 15:01:09 2020 -0600 + + ec2: Do not log IMDSv2 token values, instead use REDACTED (#219) + + Instead of logging the token values used log the headers and replace the actual + values with the string 'REDACTED'. This allows users to examine cloud-init.log + and see that the IMDSv2 token header is being used but avoids leaving the value + used in the log file itself. + + LP: #1863943 + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + cloudinit/ec2_utils.py | 12 ++++++++-- + cloudinit/sources/DataSourceEc2.py | 35 +++++++++++++++++++---------- + cloudinit/url_helper.py | 27 ++++++++++++++++------ + tests/unittests/test_datasource/test_ec2.py | 17 ++++++++++++++ + 4 files changed, 70 insertions(+), 21 deletions(-) + +diff --git a/cloudinit/ec2_utils.py b/cloudinit/ec2_utils.py +index 57708c1..34acfe8 100644 +--- a/cloudinit/ec2_utils.py ++++ b/cloudinit/ec2_utils.py +@@ -142,7 +142,8 @@ def skip_retry_on_codes(status_codes, _request_args, cause): + def get_instance_userdata(api_version='latest', + metadata_address='http://169.254.169.254', + ssl_details=None, timeout=5, retries=5, +- headers_cb=None, exception_cb=None): ++ headers_cb=None, headers_redact=None, ++ exception_cb=None): + ud_url = url_helper.combine_url(metadata_address, api_version) + ud_url = url_helper.combine_url(ud_url, 'user-data') + user_data = '' +@@ -155,7 +156,8 @@ def get_instance_userdata(api_version='latest', + SKIP_USERDATA_CODES) + response = url_helper.read_file_or_url( + ud_url, ssl_details=ssl_details, timeout=timeout, +- retries=retries, exception_cb=exception_cb, headers_cb=headers_cb) ++ retries=retries, exception_cb=exception_cb, headers_cb=headers_cb, ++ headers_redact=headers_redact) + user_data = response.contents + except url_helper.UrlError as e: + if e.code not in SKIP_USERDATA_CODES: +@@ -169,11 +171,13 @@ def _get_instance_metadata(tree, api_version='latest', + metadata_address='http://169.254.169.254', + ssl_details=None, timeout=5, retries=5, + leaf_decoder=None, headers_cb=None, ++ headers_redact=None, + exception_cb=None): + md_url = url_helper.combine_url(metadata_address, api_version, tree) + caller = functools.partial( + url_helper.read_file_or_url, ssl_details=ssl_details, + timeout=timeout, retries=retries, headers_cb=headers_cb, ++ headers_redact=headers_redact, + exception_cb=exception_cb) + + def mcaller(url): +@@ -197,6 +201,7 @@ def get_instance_metadata(api_version='latest', + metadata_address='http://169.254.169.254', + ssl_details=None, timeout=5, retries=5, + leaf_decoder=None, headers_cb=None, ++ headers_redact=None, + exception_cb=None): + # Note, 'meta-data' explicitly has trailing /. + # this is required for CloudStack (LP: #1356855) +@@ -204,6 +209,7 @@ def get_instance_metadata(api_version='latest', + metadata_address=metadata_address, + ssl_details=ssl_details, timeout=timeout, + retries=retries, leaf_decoder=leaf_decoder, ++ headers_redact=headers_redact, + headers_cb=headers_cb, + exception_cb=exception_cb) + +@@ -212,12 +218,14 @@ def get_instance_identity(api_version='latest', + metadata_address='http://169.254.169.254', + ssl_details=None, timeout=5, retries=5, + leaf_decoder=None, headers_cb=None, ++ headers_redact=None, + exception_cb=None): + return _get_instance_metadata(tree='dynamic/instance-identity', + api_version=api_version, + metadata_address=metadata_address, + ssl_details=ssl_details, timeout=timeout, + retries=retries, leaf_decoder=leaf_decoder, ++ headers_redact=headers_redact, + headers_cb=headers_cb, + exception_cb=exception_cb) + # vi: ts=4 expandtab +diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py +index b9f346a..0f2bfef 100644 +--- a/cloudinit/sources/DataSourceEc2.py ++++ b/cloudinit/sources/DataSourceEc2.py +@@ -31,6 +31,9 @@ STRICT_ID_DEFAULT = "warn" + API_TOKEN_ROUTE = 'latest/api/token' + API_TOKEN_DISABLED = '_ec2_disable_api_token' + AWS_TOKEN_TTL_SECONDS = '21600' ++AWS_TOKEN_PUT_HEADER = 'X-aws-ec2-metadata-token' ++AWS_TOKEN_REQ_HEADER = AWS_TOKEN_PUT_HEADER + '-ttl-seconds' ++AWS_TOKEN_REDACT = [AWS_TOKEN_PUT_HEADER, AWS_TOKEN_REQ_HEADER] + + + class CloudNames(object): +@@ -158,7 +161,8 @@ class DataSourceEc2(sources.DataSource): + for api_ver in self.extended_metadata_versions: + url = url_tmpl.format(self.metadata_address, api_ver) + try: +- resp = uhelp.readurl(url=url, headers=headers) ++ resp = uhelp.readurl(url=url, headers=headers, ++ headers_redact=AWS_TOKEN_REDACT) + except uhelp.UrlError as e: + LOG.debug('url %s raised exception %s', url, e) + else: +@@ -180,6 +184,7 @@ class DataSourceEc2(sources.DataSource): + self.identity = ec2.get_instance_identity( + api_version, self.metadata_address, + headers_cb=self._get_headers, ++ headers_redact=AWS_TOKEN_REDACT, + exception_cb=self._refresh_stale_aws_token_cb).get( + 'document', {}) + return self.identity.get( +@@ -205,7 +210,8 @@ class DataSourceEc2(sources.DataSource): + LOG.debug('Fetching Ec2 IMDSv2 API Token') + url, response = uhelp.wait_for_url( + urls=urls, max_wait=1, timeout=1, status_cb=self._status_cb, +- headers_cb=self._get_headers, request_method=request_method) ++ headers_cb=self._get_headers, request_method=request_method, ++ headers_redact=AWS_TOKEN_REDACT) + + if url and response: + self._api_token = response +@@ -252,7 +258,8 @@ class DataSourceEc2(sources.DataSource): + url, _ = uhelp.wait_for_url( + urls=urls, max_wait=url_params.max_wait_seconds, + timeout=url_params.timeout_seconds, status_cb=LOG.warning, +- headers_cb=self._get_headers, request_method=request_method) ++ headers_redact=AWS_TOKEN_REDACT, headers_cb=self._get_headers, ++ request_method=request_method) + + if url: + metadata_address = url2base[url] +@@ -420,6 +427,7 @@ class DataSourceEc2(sources.DataSource): + if not self.wait_for_metadata_service(): + return {} + api_version = self.get_metadata_api_version() ++ redact = AWS_TOKEN_REDACT + crawled_metadata = {} + if self.cloud_name == CloudNames.AWS: + exc_cb = self._refresh_stale_aws_token_cb +@@ -429,14 +437,17 @@ class DataSourceEc2(sources.DataSource): + try: + crawled_metadata['user-data'] = ec2.get_instance_userdata( + api_version, self.metadata_address, +- headers_cb=self._get_headers, exception_cb=exc_cb_ud) ++ headers_cb=self._get_headers, headers_redact=redact, ++ exception_cb=exc_cb_ud) + crawled_metadata['meta-data'] = ec2.get_instance_metadata( + api_version, self.metadata_address, +- headers_cb=self._get_headers, exception_cb=exc_cb) ++ headers_cb=self._get_headers, headers_redact=redact, ++ exception_cb=exc_cb) + if self.cloud_name == CloudNames.AWS: + identity = ec2.get_instance_identity( + api_version, self.metadata_address, +- headers_cb=self._get_headers, exception_cb=exc_cb) ++ headers_cb=self._get_headers, headers_redact=redact, ++ exception_cb=exc_cb) + crawled_metadata['dynamic'] = {'instance-identity': identity} + except Exception: + util.logexc( +@@ -455,11 +466,12 @@ class DataSourceEc2(sources.DataSource): + if self.cloud_name != CloudNames.AWS: + return None + LOG.debug("Refreshing Ec2 metadata API token") +- request_header = {'X-aws-ec2-metadata-token-ttl-seconds': seconds} ++ request_header = {AWS_TOKEN_REQ_HEADER: seconds} + token_url = '{}/{}'.format(self.metadata_address, API_TOKEN_ROUTE) + try: +- response = uhelp.readurl( +- token_url, headers=request_header, request_method="PUT") ++ response = uhelp.readurl(token_url, headers=request_header, ++ headers_redact=AWS_TOKEN_REDACT, ++ request_method="PUT") + except uhelp.UrlError as e: + LOG.warning( + 'Unable to get API token: %s raised exception %s', +@@ -500,8 +512,7 @@ class DataSourceEc2(sources.DataSource): + API_TOKEN_DISABLED): + return {} + # Request a 6 hour token if URL is API_TOKEN_ROUTE +- request_token_header = { +- 'X-aws-ec2-metadata-token-ttl-seconds': AWS_TOKEN_TTL_SECONDS} ++ request_token_header = {AWS_TOKEN_REQ_HEADER: AWS_TOKEN_TTL_SECONDS} + if API_TOKEN_ROUTE in url: + return request_token_header + if not self._api_token: +@@ -511,7 +522,7 @@ class DataSourceEc2(sources.DataSource): + self._api_token = self._refresh_api_token() + if not self._api_token: + return {} +- return {'X-aws-ec2-metadata-token': self._api_token} ++ return {AWS_TOKEN_PUT_HEADER: self._api_token} + + + class DataSourceEc2Local(DataSourceEc2): +diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py +index 1496a47..3e7de9f 100644 +--- a/cloudinit/url_helper.py ++++ b/cloudinit/url_helper.py +@@ -8,6 +8,7 @@ + # + # This file is part of cloud-init. See LICENSE file for license information. + ++import copy + import json + import os + import requests +@@ -41,6 +42,7 @@ else: + SSL_ENABLED = False + CONFIG_ENABLED = False # This was added in 0.7 (but taken out in >=1.0) + _REQ_VER = None ++REDACTED = 'REDACTED' + try: + from distutils.version import LooseVersion + import pkg_resources +@@ -199,9 +201,9 @@ def _get_ssl_args(url, ssl_details): + + + def readurl(url, data=None, timeout=None, retries=0, sec_between=1, +- headers=None, headers_cb=None, ssl_details=None, +- check_status=True, allow_redirects=True, exception_cb=None, +- session=None, infinite=False, log_req_resp=True, ++ headers=None, headers_cb=None, headers_redact=None, ++ ssl_details=None, check_status=True, allow_redirects=True, ++ exception_cb=None, session=None, infinite=False, log_req_resp=True, + request_method=None): + """Wrapper around requests.Session to read the url and retry if necessary + +@@ -217,6 +219,7 @@ def readurl(url, data=None, timeout=None, retries=0, sec_between=1, + :param headers: Optional dict of headers to send during request + :param headers_cb: Optional callable returning a dict of values to send as + headers during request ++ :param headers_redact: Optional list of header names to redact from the log + :param ssl_details: Optional dict providing key_file, ca_certs, and + cert_file keys for use on in ssl connections. + :param check_status: Optional boolean set True to raise when HTTPError +@@ -243,6 +246,8 @@ def readurl(url, data=None, timeout=None, retries=0, sec_between=1, + req_args['method'] = request_method + if timeout is not None: + req_args['timeout'] = max(float(timeout), 0) ++ if headers_redact is None: ++ headers_redact = [] + # It doesn't seem like config + # was added in older library versions (or newer ones either), thus we + # need to manually do the retries if it wasn't... +@@ -287,6 +292,12 @@ def readurl(url, data=None, timeout=None, retries=0, sec_between=1, + if k == 'data': + continue + filtered_req_args[k] = v ++ if k == 'headers': ++ for hkey, _hval in v.items(): ++ if hkey in headers_redact: ++ filtered_req_args[k][hkey] = ( ++ copy.deepcopy(req_args[k][hkey])) ++ filtered_req_args[k][hkey] = REDACTED + try: + + if log_req_resp: +@@ -339,8 +350,8 @@ def readurl(url, data=None, timeout=None, retries=0, sec_between=1, + return None # Should throw before this... + + +-def wait_for_url(urls, max_wait=None, timeout=None, +- status_cb=None, headers_cb=None, sleep_time=1, ++def wait_for_url(urls, max_wait=None, timeout=None, status_cb=None, ++ headers_cb=None, headers_redact=None, sleep_time=1, + exception_cb=None, sleep_time_cb=None, request_method=None): + """ + urls: a list of urls to try +@@ -352,6 +363,7 @@ def wait_for_url(urls, max_wait=None, timeout=None, + status_cb: call method with string message when a url is not available + headers_cb: call method with single argument of url to get headers + for request. ++ headers_redact: a list of header names to redact from the log + exception_cb: call method with 2 arguments 'msg' (per status_cb) and + 'exception', the exception that occurred. + sleep_time_cb: call method with 2 arguments (response, loop_n) that +@@ -415,8 +427,9 @@ def wait_for_url(urls, max_wait=None, timeout=None, + headers = {} + + response = readurl( +- url, headers=headers, timeout=timeout, +- check_status=False, request_method=request_method) ++ url, headers=headers, headers_redact=headers_redact, ++ timeout=timeout, check_status=False, ++ request_method=request_method) + if not response.contents: + reason = "empty response [%s]" % (response.code) + url_exc = UrlError(ValueError(reason), code=response.code, +diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py +index 34a089f..bd5bd4c 100644 +--- a/tests/unittests/test_datasource/test_ec2.py ++++ b/tests/unittests/test_datasource/test_ec2.py +@@ -429,6 +429,23 @@ class TestEc2(test_helpers.HttprettyTestCase): + self.assertTrue(ds.get_data()) + self.assertFalse(ds.is_classic_instance()) + ++ def test_aws_token_redacted(self): ++ """Verify that aws tokens are redacted when logged.""" ++ ds = self._setup_ds( ++ platform_data=self.valid_platform_data, ++ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}}, ++ md={'md': DEFAULT_METADATA}) ++ self.assertTrue(ds.get_data()) ++ all_logs = self.logs.getvalue().splitlines() ++ REDACT_TTL = "'X-aws-ec2-metadata-token-ttl-seconds': 'REDACTED'" ++ REDACT_TOK = "'X-aws-ec2-metadata-token': 'REDACTED'" ++ logs_with_redacted_ttl = [log for log in all_logs if REDACT_TTL in log] ++ logs_with_redacted = [log for log in all_logs if REDACT_TOK in log] ++ logs_with_token = [log for log in all_logs if 'API-TOKEN' in log] ++ self.assertEqual(1, len(logs_with_redacted_ttl)) ++ self.assertEqual(79, len(logs_with_redacted)) ++ self.assertEqual(0, len(logs_with_token)) ++ + @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery') + def test_valid_platform_with_strict_true(self, m_dhcp): + """Valid platform data should return true with strict_id true.""" +-- +1.8.3.1 + diff --git a/SOURCES/ci-ec2-only-redact-token-request-headers-in-logs-avoid-.patch b/SOURCES/ci-ec2-only-redact-token-request-headers-in-logs-avoid-.patch new file mode 100644 index 0000000..1e10436 --- /dev/null +++ b/SOURCES/ci-ec2-only-redact-token-request-headers-in-logs-avoid-.patch @@ -0,0 +1,125 @@ +From 7715b4ac2a105298e8b7ca5e13761646d0844471 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Fri, 26 Jun 2020 09:51:07 +0200 +Subject: [PATCH 2/2] ec2: only redact token request headers in logs, avoid + altering request (#230) + +RH-Author: Eduardo Otubo +Message-id: <20200624114403.2164-3-otubo@redhat.com> +Patchwork-id: 97795 +O-Subject: [RHEL-8.2.1 cloud-init PATCH 2/2] ec2: only redact token request headers in logs, avoid altering request (#230) +Bugzilla: 1850456 +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Mohammed Gamal + +commit fa1abfec27050a4fb71cad950a17e42f9b43b478 +Author: Chad Smith +Date: Tue Mar 3 15:23:33 2020 -0700 + + ec2: only redact token request headers in logs, avoid altering request (#230) + + Our header redact logic was redacting both logged request headers and + the actual source request. This results in DataSourceEc2 sending the + invalid header "X-aws-ec2-metadata-token-ttl-seconds: REDACTED" which + gets an HTTP status response of 400. + + Cloud-init retries this failed token request for 2 minutes before + falling back to IMDSv1. + + LP: #1865882 + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + cloudinit/tests/test_url_helper.py | 34 +++++++++++++++++++++++++++++++++- + cloudinit/url_helper.py | 15 ++++++++------- + 2 files changed, 41 insertions(+), 8 deletions(-) + +diff --git a/cloudinit/tests/test_url_helper.py b/cloudinit/tests/test_url_helper.py +index 1674120..29b3937 100644 +--- a/cloudinit/tests/test_url_helper.py ++++ b/cloudinit/tests/test_url_helper.py +@@ -1,7 +1,8 @@ + # This file is part of cloud-init. See LICENSE file for license information. + + from cloudinit.url_helper import ( +- NOT_FOUND, UrlError, oauth_headers, read_file_or_url, retry_on_url_exc) ++ NOT_FOUND, UrlError, REDACTED, oauth_headers, read_file_or_url, ++ retry_on_url_exc) + from cloudinit.tests.helpers import CiTestCase, mock, skipIf + from cloudinit import util + from cloudinit import version +@@ -50,6 +51,9 @@ class TestOAuthHeaders(CiTestCase): + + + class TestReadFileOrUrl(CiTestCase): ++ ++ with_logs = True ++ + def test_read_file_or_url_str_from_file(self): + """Test that str(result.contents) on file is text version of contents. + It should not be "b'data'", but just "'data'" """ +@@ -71,6 +75,34 @@ class TestReadFileOrUrl(CiTestCase): + self.assertEqual(result.contents, data) + self.assertEqual(str(result), data.decode('utf-8')) + ++ @httpretty.activate ++ def test_read_file_or_url_str_from_url_redacting_headers_from_logs(self): ++ """Headers are redacted from logs but unredacted in requests.""" ++ url = 'http://hostname/path' ++ headers = {'sensitive': 'sekret', 'server': 'blah'} ++ httpretty.register_uri(httpretty.GET, url) ++ ++ read_file_or_url(url, headers=headers, headers_redact=['sensitive']) ++ logs = self.logs.getvalue() ++ for k in headers.keys(): ++ self.assertEqual(headers[k], httpretty.last_request().headers[k]) ++ self.assertIn(REDACTED, logs) ++ self.assertNotIn('sekret', logs) ++ ++ @httpretty.activate ++ def test_read_file_or_url_str_from_url_redacts_noheaders(self): ++ """When no headers_redact, header values are in logs and requests.""" ++ url = 'http://hostname/path' ++ headers = {'sensitive': 'sekret', 'server': 'blah'} ++ httpretty.register_uri(httpretty.GET, url) ++ ++ read_file_or_url(url, headers=headers) ++ for k in headers.keys(): ++ self.assertEqual(headers[k], httpretty.last_request().headers[k]) ++ logs = self.logs.getvalue() ++ self.assertNotIn(REDACTED, logs) ++ self.assertIn('sekret', logs) ++ + @mock.patch(M_PATH + 'readurl') + def test_read_file_or_url_passes_params_to_readurl(self, m_readurl): + """read_file_or_url passes all params through to readurl.""" +diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py +index 3e7de9f..e6188ea 100644 +--- a/cloudinit/url_helper.py ++++ b/cloudinit/url_helper.py +@@ -291,13 +291,14 @@ def readurl(url, data=None, timeout=None, retries=0, sec_between=1, + for (k, v) in req_args.items(): + if k == 'data': + continue +- filtered_req_args[k] = v +- if k == 'headers': +- for hkey, _hval in v.items(): +- if hkey in headers_redact: +- filtered_req_args[k][hkey] = ( +- copy.deepcopy(req_args[k][hkey])) +- filtered_req_args[k][hkey] = REDACTED ++ if k == 'headers' and headers_redact: ++ matched_headers = [k for k in headers_redact if v.get(k)] ++ if matched_headers: ++ filtered_req_args[k] = copy.deepcopy(v) ++ for key in matched_headers: ++ filtered_req_args[k][key] = REDACTED ++ else: ++ filtered_req_args[k] = v + try: + + if log_req_resp: +-- +1.8.3.1 + diff --git a/SOURCES/ci-net-IPv6-accept_ra-slaac-stateless-51.patch b/SOURCES/ci-net-IPv6-accept_ra-slaac-stateless-51.patch deleted file mode 100644 index dd2aa5b..0000000 --- a/SOURCES/ci-net-IPv6-accept_ra-slaac-stateless-51.patch +++ /dev/null @@ -1,667 +0,0 @@ -From f14ed869e5784b1d5a3dfbabc1484eb266e8c3ab Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 4 May 2020 12:40:13 +0200 -Subject: [PATCH 6/6] net: IPv6, accept_ra, slaac, stateless (#51) - -RH-Author: Eduardo Otubo -Message-id: <20200327152826.13343-7-otubo@redhat.com> -Patchwork-id: 94458 -O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 6/6] net: IPv6, accept_ra, slaac, stateless (#51) -Bugzilla: 1811753 -RH-Acked-by: Cathy Avery -RH-Acked-by: Vitaly Kuznetsov - -commit 62bbc262c3c7f633eac1d09ec78c055eef05166a -Author: Harald -Date: Wed Nov 20 18:55:27 2019 +0100 - - net: IPv6, accept_ra, slaac, stateless (#51) - - Router advertisements are required for the default route - to be set up, thus accept_ra should be enabled for - dhcpv6-stateful. - - sysconf: IPV6_FORCE_ACCEPT_RA controls accept_ra sysctl. - eni: mode static and mode dhcp 'accept_ra' controls sysctl. - - Add 'accept-ra: true|false' parameter to config v1 and - v2. When True: accept_ra is set to '1'. When False: - accept_ra is set to '0'. When not defined in config the - value is left to the operating system default. - - This change also extend the IPv6 support to distinguish - between slaac and dhcpv6-stateless. SLAAC is autoconfig - without any options from DHCP, while stateless auto-configures - the address and the uses DHCP for other options. - - LP: #1806014 - LP: #1808647 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/eni.py | 15 ++ - cloudinit/net/netplan.py | 9 +- - cloudinit/net/network_state.py | 21 +- - cloudinit/net/sysconfig.py | 34 ++- - cloudinit/sources/helpers/openstack.py | 21 +- - .../unittests/test_datasource/test_configdrive.py | 3 +- - tests/unittests/test_net.py | 249 +++++++++++++++++++++ - 7 files changed, 334 insertions(+), 18 deletions(-) - -diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py -index 896a39b..c70435a 100644 ---- a/cloudinit/net/eni.py -+++ b/cloudinit/net/eni.py -@@ -393,6 +393,7 @@ class Renderer(renderer.Renderer): - def _render_iface(self, iface, render_hwaddress=False): - sections = [] - subnets = iface.get('subnets', {}) -+ accept_ra = iface.pop('accept-ra', None) - if subnets: - for index, subnet in enumerate(subnets): - ipv4_subnet_mtu = None -@@ -409,9 +410,23 @@ class Renderer(renderer.Renderer): - subnet['type'] == 'ipv6_dhcpv6-stateful'): - # Configure network settings using DHCP or DHCPv6 - iface['mode'] = 'dhcp' -+ if accept_ra is not None: -+ # Accept router advertisements (0=off, 1=on) -+ iface['accept_ra'] = '1' if accept_ra else '0' - elif subnet['type'] == 'ipv6_dhcpv6-stateless': - # Configure network settings using SLAAC from RAs - iface['mode'] = 'auto' -+ # Use stateless DHCPv6 (0=off, 1=on) -+ iface['dhcp'] = '1' -+ elif subnet['type'] == 'ipv6_slaac': -+ # Configure network settings using SLAAC from RAs -+ iface['mode'] = 'auto' -+ # Use stateless DHCPv6 (0=off, 1=on) -+ iface['dhcp'] = '0' -+ elif subnet_is_ipv6(subnet) and subnet['type'] == 'static': -+ if accept_ra is not None: -+ # Accept router advertisements (0=off, 1=on) -+ iface['accept_ra'] = '1' if accept_ra else '0' - - # do not emit multiple 'auto $IFACE' lines as older (precise) - # ifupdown complains -diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py -index 21517fd..78ec38c 100644 ---- a/cloudinit/net/netplan.py -+++ b/cloudinit/net/netplan.py -@@ -4,7 +4,7 @@ import copy - import os - - from . import renderer --from .network_state import subnet_is_ipv6, NET_CONFIG_TO_V2 -+from .network_state import subnet_is_ipv6, NET_CONFIG_TO_V2, IPV6_DYNAMIC_TYPES - - from cloudinit import log as logging - from cloudinit import util -@@ -51,7 +51,8 @@ def _extract_addresses(config, entry, ifname): - 'mtu': 1480, - 'netmask': 64, - 'type': 'static'}], -- 'type: physical' -+ 'type: physical', -+ 'accept-ra': 'true' - } - - An entry dictionary looks like: -@@ -92,6 +93,8 @@ def _extract_addresses(config, entry, ifname): - if sn_type == 'dhcp': - sn_type += '4' - entry.update({sn_type: True}) -+ elif sn_type in IPV6_DYNAMIC_TYPES: -+ entry.update({'dhcp6': True}) - elif sn_type in ['static']: - addr = "%s" % subnet.get('address') - if 'prefix' in subnet: -@@ -144,6 +147,8 @@ def _extract_addresses(config, entry, ifname): - ns = entry.get('nameservers', {}) - ns.update({'search': searchdomains}) - entry.update({'nameservers': ns}) -+ if 'accept-ra' in config and config['accept-ra'] is not None: -+ entry.update({'accept-ra': util.is_true(config.get('accept-ra'))}) - - - def _extract_bond_slaves_by_name(interfaces, entry, bond_master): -diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py -index 571eb57..82cfa42 100644 ---- a/cloudinit/net/network_state.py -+++ b/cloudinit/net/network_state.py -@@ -17,13 +17,17 @@ from cloudinit import util - LOG = logging.getLogger(__name__) - - NETWORK_STATE_VERSION = 1 -+IPV6_DYNAMIC_TYPES = ['dhcp6', -+ 'ipv6_slaac', -+ 'ipv6_dhcpv6-stateless', -+ 'ipv6_dhcpv6-stateful'] - NETWORK_STATE_REQUIRED_KEYS = { - 1: ['version', 'config', 'network_state'], - } - NETWORK_V2_KEY_FILTER = [ - 'addresses', 'dhcp4', 'dhcp4-overrides', 'dhcp6', 'dhcp6-overrides', - 'gateway4', 'gateway6', 'interfaces', 'match', 'mtu', 'nameservers', -- 'renderer', 'set-name', 'wakeonlan' -+ 'renderer', 'set-name', 'wakeonlan', 'accept-ra' - ] - - NET_CONFIG_TO_V2 = { -@@ -341,7 +345,8 @@ class NetworkStateInterpreter(object): - 'name': 'eth0', - 'subnets': [ - {'type': 'dhcp4'} -- ] -+ ], -+ 'accept-ra': 'true' - } - ''' - -@@ -361,6 +366,9 @@ class NetworkStateInterpreter(object): - self.use_ipv6 = True - break - -+ accept_ra = command.get('accept-ra', None) -+ if accept_ra is not None: -+ accept_ra = util.is_true(accept_ra) - iface.update({ - 'name': command.get('name'), - 'type': command.get('type'), -@@ -371,6 +379,7 @@ class NetworkStateInterpreter(object): - 'address': None, - 'gateway': None, - 'subnets': subnets, -+ 'accept-ra': accept_ra - }) - self._network_state['interfaces'].update({command.get('name'): iface}) - self.dump_network_state() -@@ -614,6 +623,7 @@ class NetworkStateInterpreter(object): - driver: ixgbe - set-name: lom1 - dhcp6: true -+ accept-ra: true - switchports: - match: - name: enp2* -@@ -642,7 +652,7 @@ class NetworkStateInterpreter(object): - driver = match.get('driver', None) - if driver: - phy_cmd['params'] = {'driver': driver} -- for key in ['mtu', 'match', 'wakeonlan']: -+ for key in ['mtu', 'match', 'wakeonlan', 'accept-ra']: - if key in cfg: - phy_cmd[key] = cfg[key] - -@@ -915,8 +925,9 @@ def is_ipv6_addr(address): - - def subnet_is_ipv6(subnet): - """Common helper for checking network_state subnets for ipv6.""" -- # 'static6' or 'dhcp6' -- if subnet['type'].endswith('6'): -+ # 'static6', 'dhcp6', 'ipv6_dhcpv6-stateful', 'ipv6_dhcpv6-stateless' or -+ # 'ipv6_slaac' -+ if subnet['type'].endswith('6') or subnet['type'] in IPV6_DYNAMIC_TYPES: - # This is a request for DHCPv6. - return True - elif subnet['type'] == 'static' and is_ipv6_addr(subnet.get('address')): -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index 8b11dbb..13c0a65 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -14,7 +14,7 @@ from configobj import ConfigObj - - from . import renderer - from .network_state import ( -- is_ipv6_addr, net_prefix_to_ipv4_mask, subnet_is_ipv6) -+ is_ipv6_addr, net_prefix_to_ipv4_mask, subnet_is_ipv6, IPV6_DYNAMIC_TYPES) - - LOG = logging.getLogger(__name__) - NM_CFG_FILE = "/etc/NetworkManager/NetworkManager.conf" -@@ -319,6 +319,9 @@ class Renderer(renderer.Renderer): - continue - iface_cfg[new_key] = old_value - -+ if iface['accept-ra'] is not None: -+ iface_cfg['IPV6_FORCE_ACCEPT_RA'] = iface['accept-ra'] -+ - @classmethod - def _render_subnets(cls, iface_cfg, subnets, has_default_route): - # setting base values -@@ -335,6 +338,15 @@ class Renderer(renderer.Renderer): - iface_cfg['DHCPV6C'] = True - elif subnet_type == 'ipv6_dhcpv6-stateless': - iface_cfg['IPV6INIT'] = True -+ # Configure network settings using SLAAC from RAs and optional -+ # info from dhcp server using DHCPv6 -+ iface_cfg['IPV6_AUTOCONF'] = True -+ iface_cfg['DHCPV6C'] = True -+ # Use Information-request to get only stateless configuration -+ # parameters (i.e., without address). -+ iface_cfg['DHCPV6C_OPTIONS'] = '-S' -+ elif subnet_type == 'ipv6_slaac': -+ iface_cfg['IPV6INIT'] = True - # Configure network settings using SLAAC from RAs - iface_cfg['IPV6_AUTOCONF'] = True - elif subnet_type in ['dhcp4', 'dhcp']: -@@ -381,10 +393,15 @@ class Renderer(renderer.Renderer): - # metric may apply to both dhcp and static config - if 'metric' in subnet: - iface_cfg['METRIC'] = subnet['metric'] -+ # TODO(hjensas): Including dhcp6 here is likely incorrect. DHCPv6 -+ # does not ever provide a default gateway, the default gateway -+ # come from RA's. (https://github.com/openSUSE/wicked/issues/570) - if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']: - if has_default_route and iface_cfg['BOOTPROTO'] != 'none': - iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False - continue -+ elif subnet_type in IPV6_DYNAMIC_TYPES: -+ continue - elif subnet_type == 'static': - if subnet_is_ipv6(subnet): - ipv6_index = ipv6_index + 1 -@@ -424,10 +441,14 @@ class Renderer(renderer.Renderer): - @classmethod - def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): - for _, subnet in enumerate(subnets, start=len(iface_cfg.children)): -+ subnet_type = subnet.get('type') - for route in subnet.get('routes', []): - is_ipv6 = subnet.get('ipv6') or is_ipv6_addr(route['gateway']) - -- if _is_default_route(route): -+ # Any dynamic configuration method, slaac, dhcpv6-stateful/ -+ # stateless should get router information from router RA's. -+ if (_is_default_route(route) and subnet_type not in -+ IPV6_DYNAMIC_TYPES): - if ( - (subnet.get('ipv4') and - route_cfg.has_set_default_ipv4) or -@@ -446,10 +467,17 @@ class Renderer(renderer.Renderer): - # TODO(harlowja): add validation that no other iface has - # also provided the default route? - iface_cfg['DEFROUTE'] = True -+ # TODO(hjensas): Including dhcp6 here is likely incorrect. -+ # DHCPv6 does not ever provide a default gateway, the -+ # default gateway come from RA's. -+ # (https://github.com/openSUSE/wicked/issues/570) - if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4', 'dhcp6'): -+ # NOTE(hjensas): DHCLIENT_SET_DEFAULT_ROUTE is SuSE -+ # only. RHEL, CentOS, Fedora does not implement this -+ # option. - iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = True - if 'gateway' in route: -- if is_ipv6 or is_ipv6_addr(route['gateway']): -+ if is_ipv6: - iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] - route_cfg.has_set_default_ipv6 = True - else: -diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py -index 9f2fd2d..77fcdd3 100644 ---- a/cloudinit/sources/helpers/openstack.py -+++ b/cloudinit/sources/helpers/openstack.py -@@ -584,17 +584,24 @@ def convert_net_json(network_json=None, known_macs=None): - if n['link'] == link['id']]: - subnet = dict((k, v) for k, v in network.items() - if k in valid_keys['subnet']) -- if 'dhcp' in network['type']: -- t = (network['type'] if network['type'].startswith('ipv6') -- else 'dhcp4') -- subnet.update({ -- 'type': t, -- }) -- else: -+ -+ if network['type'] == 'ipv4_dhcp': -+ subnet.update({'type': 'dhcp4'}) -+ elif network['type'] == 'ipv6_dhcp': -+ subnet.update({'type': 'dhcp6'}) -+ elif network['type'] in ['ipv6_slaac', 'ipv6_dhcpv6-stateless', -+ 'ipv6_dhcpv6-stateful']: -+ subnet.update({'type': network['type']}) -+ elif network['type'] in ['ipv4', 'ipv6']: - subnet.update({ - 'type': 'static', - 'address': network.get('ip_address'), - }) -+ -+ # Enable accept_ra for stateful and legacy ipv6_dhcp types -+ if network['type'] in ['ipv6_dhcpv6-stateful', 'ipv6_dhcp']: -+ cfg.update({'accept-ra': True}) -+ - if network['type'] == 'ipv4': - subnet['ipv4'] = True - if network['type'] == 'ipv6': -diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py -index ed4e9d5..bd4c310 100644 ---- a/tests/unittests/test_datasource/test_configdrive.py -+++ b/tests/unittests/test_datasource/test_configdrive.py -@@ -536,7 +536,8 @@ class TestNetJson(CiTestCase): - 'mtu': None, - 'name': 'enp0s2', - 'subnets': [{'type': 'ipv6_dhcpv6-stateful'}], -- 'type': 'physical'} -+ 'type': 'physical', -+ 'accept-ra': True} - ], - } - conv_data = openstack.convert_net_json(in_data, known_macs=KNOWN_MACS) -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 70d13f3..21a3f0e 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -712,6 +712,143 @@ NETWORK_CONFIGS = { - """), - }, - }, -+ 'dhcpv6_accept_ra': { -+ 'expected_eni': textwrap.dedent("""\ -+ auto lo -+ iface lo inet loopback -+ -+ auto iface0 -+ iface iface0 inet6 dhcp -+ accept_ra 1 -+ """).rstrip(' '), -+ 'expected_netplan': textwrap.dedent(""" -+ network: -+ version: 2 -+ ethernets: -+ iface0: -+ accept-ra: true -+ dhcp6: true -+ """).rstrip(' '), -+ 'yaml_v1': textwrap.dedent("""\ -+ version: 1 -+ config: -+ - type: 'physical' -+ name: 'iface0' -+ subnets: -+ - {'type': 'dhcp6'} -+ accept-ra: true -+ """).rstrip(' '), -+ 'yaml_v2': textwrap.dedent("""\ -+ version: 2 -+ ethernets: -+ iface0: -+ dhcp6: true -+ accept-ra: true -+ """).rstrip(' '), -+ 'expected_sysconfig': { -+ 'ifcfg-iface0': textwrap.dedent("""\ -+ BOOTPROTO=none -+ DEVICE=iface0 -+ DHCPV6C=yes -+ IPV6INIT=yes -+ IPV6_FORCE_ACCEPT_RA=yes -+ DEVICE=iface0 -+ NM_CONTROLLED=no -+ ONBOOT=yes -+ STARTMODE=auto -+ TYPE=Ethernet -+ USERCTL=no -+ """), -+ }, -+ }, -+ 'dhcpv6_reject_ra': { -+ 'expected_eni': textwrap.dedent("""\ -+ auto lo -+ iface lo inet loopback -+ -+ auto iface0 -+ iface iface0 inet6 dhcp -+ accept_ra 0 -+ """).rstrip(' '), -+ 'expected_netplan': textwrap.dedent(""" -+ network: -+ version: 2 -+ ethernets: -+ iface0: -+ accept-ra: false -+ dhcp6: true -+ """).rstrip(' '), -+ 'yaml_v1': textwrap.dedent("""\ -+ version: 1 -+ config: -+ - type: 'physical' -+ name: 'iface0' -+ subnets: -+ - {'type': 'dhcp6'} -+ accept-ra: false -+ """).rstrip(' '), -+ 'yaml_v2': textwrap.dedent("""\ -+ version: 2 -+ ethernets: -+ iface0: -+ dhcp6: true -+ accept-ra: false -+ """).rstrip(' '), -+ 'expected_sysconfig': { -+ 'ifcfg-iface0': textwrap.dedent("""\ -+ BOOTPROTO=none -+ DEVICE=iface0 -+ DHCPV6C=yes -+ IPV6INIT=yes -+ IPV6_FORCE_ACCEPT_RA=no -+ DEVICE=iface0 -+ NM_CONTROLLED=no -+ ONBOOT=yes -+ STARTMODE=auto -+ TYPE=Ethernet -+ USERCTL=no -+ """), -+ }, -+ }, -+ 'ipv6_slaac': { -+ 'expected_eni': textwrap.dedent("""\ -+ auto lo -+ iface lo inet loopback -+ -+ auto iface0 -+ iface iface0 inet6 auto -+ dhcp 0 -+ """).rstrip(' '), -+ 'expected_netplan': textwrap.dedent(""" -+ network: -+ version: 2 -+ ethernets: -+ iface0: -+ dhcp6: true -+ """).rstrip(' '), -+ 'yaml': textwrap.dedent("""\ -+ version: 1 -+ config: -+ - type: 'physical' -+ name: 'iface0' -+ subnets: -+ - {'type': 'ipv6_slaac'} -+ """).rstrip(' '), -+ 'expected_sysconfig': { -+ 'ifcfg-iface0': textwrap.dedent("""\ -+ BOOTPROTO=none -+ DEVICE=iface0 -+ IPV6_AUTOCONF=yes -+ IPV6INIT=yes -+ DEVICE=iface0 -+ NM_CONTROLLED=no -+ ONBOOT=yes -+ STARTMODE=auto -+ TYPE=Ethernet -+ USERCTL=no -+ """), -+ }, -+ }, - 'dhcpv6_stateless': { - 'expected_eni': textwrap.dedent("""\ - auto lo -@@ -719,6 +856,7 @@ NETWORK_CONFIGS = { - - auto iface0 - iface iface0 inet6 auto -+ dhcp 1 - """).rstrip(' '), - 'expected_netplan': textwrap.dedent(""" - network: -@@ -739,6 +877,8 @@ NETWORK_CONFIGS = { - 'ifcfg-iface0': textwrap.dedent("""\ - BOOTPROTO=none - DEVICE=iface0 -+ DHCPV6C=yes -+ DHCPV6C_OPTIONS=-S - IPV6_AUTOCONF=yes - IPV6INIT=yes - DEVICE=iface0 -@@ -763,6 +903,7 @@ NETWORK_CONFIGS = { - version: 2 - ethernets: - iface0: -+ accept-ra: true - dhcp6: true - """).rstrip(' '), - 'yaml': textwrap.dedent("""\ -@@ -772,6 +913,7 @@ NETWORK_CONFIGS = { - name: 'iface0' - subnets: - - {'type': 'ipv6_dhcpv6-stateful'} -+ accept-ra: true - """).rstrip(' '), - 'expected_sysconfig': { - 'ifcfg-iface0': textwrap.dedent("""\ -@@ -779,6 +921,7 @@ NETWORK_CONFIGS = { - DEVICE=iface0 - DHCPV6C=yes - IPV6INIT=yes -+ IPV6_FORCE_ACCEPT_RA=yes - DEVICE=iface0 - NM_CONTROLLED=no - ONBOOT=yes -@@ -2376,6 +2519,33 @@ USERCTL=no - } - } - -+ def test_dhcpv6_accept_ra_config_v1(self): -+ entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] -+ found = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v1'])) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) -+ -+ def test_dhcpv6_accept_ra_config_v2(self): -+ entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] -+ found = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v2'])) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) -+ -+ def test_dhcpv6_reject_ra_config_v1(self): -+ entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] -+ found = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v1'])) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) -+ -+ def test_dhcpv6_reject_ra_config_v2(self): -+ entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] -+ found = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v2'])) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) - - def test_dhcpv6_stateless_config(self): - entry = NETWORK_CONFIGS['dhcpv6_stateless'] -@@ -3267,6 +3437,46 @@ class TestNetplanRoundTrip(CiTestCase): - entry['expected_netplan'].splitlines(), - files['/etc/netplan/50-cloud-init.yaml'].splitlines()) - -+ def testsimple_render_dhcpv6_accept_ra(self): -+ entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v1'])) -+ self.assertEqual( -+ entry['expected_netplan'].splitlines(), -+ files['/etc/netplan/50-cloud-init.yaml'].splitlines()) -+ -+ def testsimple_render_dhcpv6_reject_ra(self): -+ entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v1'])) -+ self.assertEqual( -+ entry['expected_netplan'].splitlines(), -+ files['/etc/netplan/50-cloud-init.yaml'].splitlines()) -+ -+ def testsimple_render_ipv6_slaac(self): -+ entry = NETWORK_CONFIGS['ipv6_slaac'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml'])) -+ self.assertEqual( -+ entry['expected_netplan'].splitlines(), -+ files['/etc/netplan/50-cloud-init.yaml'].splitlines()) -+ -+ def testsimple_render_dhcpv6_stateless(self): -+ entry = NETWORK_CONFIGS['dhcpv6_stateless'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml'])) -+ self.assertEqual( -+ entry['expected_netplan'].splitlines(), -+ files['/etc/netplan/50-cloud-init.yaml'].splitlines()) -+ -+ def testsimple_render_dhcpv6_stateful(self): -+ entry = NETWORK_CONFIGS['dhcpv6_stateful'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml'])) -+ self.assertEqual( -+ entry['expected_netplan'].splitlines(), -+ files['/etc/netplan/50-cloud-init.yaml'].splitlines()) -+ - def testsimple_render_all(self): - entry = NETWORK_CONFIGS['all'] - files = self._render_and_read(network_config=yaml.load(entry['yaml'])) -@@ -3350,6 +3560,45 @@ class TestEniRoundTrip(CiTestCase): - entry['expected_eni'].splitlines(), - files['/etc/network/interfaces'].splitlines()) - -+ def testsimple_render_dhcpv6_stateless(self): -+ entry = NETWORK_CONFIGS['dhcpv6_stateless'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml'])) -+ files = self._render_and_read(network_config=yaml.load(entry['yaml'])) -+ self.assertEqual( -+ entry['expected_eni'].splitlines(), -+ files['/etc/network/interfaces'].splitlines()) -+ -+ def testsimple_render_ipv6_slaac(self): -+ entry = NETWORK_CONFIGS['ipv6_slaac'] -+ files = self._render_and_read(network_config=yaml.load(entry['yaml'])) -+ self.assertEqual( -+ entry['expected_eni'].splitlines(), -+ files['/etc/network/interfaces'].splitlines()) -+ -+ def testsimple_render_dhcpv6_stateful(self): -+ entry = NETWORK_CONFIGS['dhcpv6_stateless'] -+ files = self._render_and_read(network_config=yaml.load(entry['yaml'])) -+ self.assertEqual( -+ entry['expected_eni'].splitlines(), -+ files['/etc/network/interfaces'].splitlines()) -+ -+ def testsimple_render_dhcpv6_accept_ra(self): -+ entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v1'])) -+ self.assertEqual( -+ entry['expected_eni'].splitlines(), -+ files['/etc/network/interfaces'].splitlines()) -+ -+ def testsimple_render_dhcpv6_reject_ra(self): -+ entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] -+ files = self._render_and_read(network_config=yaml.load( -+ entry['yaml_v1'])) -+ self.assertEqual( -+ entry['expected_eni'].splitlines(), -+ files['/etc/network/interfaces'].splitlines()) -+ - def testsimple_render_manual(self): - """Test rendering of 'manual' for 'type' and 'control'. - --- -1.8.3.1 - diff --git a/SOURCES/ci-net-add-is_master-check-for-filtering-device-list.patch b/SOURCES/ci-net-add-is_master-check-for-filtering-device-list.patch deleted file mode 100644 index f71df31..0000000 --- a/SOURCES/ci-net-add-is_master-check-for-filtering-device-list.patch +++ /dev/null @@ -1,101 +0,0 @@ -From de86e93cc7993e068b7328bf69ec08b0aa02ca98 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 19 Feb 2020 13:32:56 +0100 -Subject: [PATCH 3/3] net: add is_master check for filtering device list - -RH-Author: Eduardo Otubo -Message-id: <20200219133256.9544-3-otubo@redhat.com> -Patchwork-id: 93937 -O-Subject: [RHEL-7.9 cloud-init PATCHv2 2/2] net: add is_master check for filtering device list -Bugzilla: 1768770 -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Vitaly Kuznetsov - -commit 059d049c57cac02cdeaca832233a19712e0b4ded -Author: Ryan Harper -Date: Tue Sep 17 10:11:00 2019 +0000 - - net: add is_master check for filtering device list - - Some network devices are transformed into a bond via kernel magic - and do not have the 'bonding' sysfs attribute, but like a bond they - have a duplicate MAC of other bond members. On Azure Advanced - Networking SRIOV devices are auto bonded and will have the same MAC - as the HyperV nic. We can detect this via the 'master' sysfs attribute - in the device sysfs path and this patch adds this to the list of devices - we ignore when enumerating device lists. - - LP: #1844191 - -Conflicts: Removing checks `has_master()' for net failover functions. Such -functions are not present because are not required so far. - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/__init__.py | 6 ++++++ - cloudinit/net/tests/test_init.py | 17 +++++++++++++++++ - 2 files changed, 23 insertions(+) - -diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py -index e758006..41659b1 100644 ---- a/cloudinit/net/__init__.py -+++ b/cloudinit/net/__init__.py -@@ -108,6 +108,10 @@ def is_bond(devname): - return os.path.exists(sys_dev_path(devname, "bonding")) - - -+def has_master(devname): -+ return os.path.exists(sys_dev_path(devname, path="master")) -+ -+ - def is_renamed(devname): - """ - /* interface name assignment types (sysfs name_assign_type attribute) */ -@@ -624,6 +628,8 @@ def get_interfaces(): - continue - if is_bond(name): - continue -+ if has_master(name): -+ continue - mac = get_interface_mac(name) - # some devices may not have a mac (tun0) - if not mac: -diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py -index f55c31e..5519867 100644 ---- a/cloudinit/net/tests/test_init.py -+++ b/cloudinit/net/tests/test_init.py -@@ -157,6 +157,12 @@ class TestReadSysNet(CiTestCase): - ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding')) - self.assertTrue(net.is_bond('eth0')) - -+ def test_has_master(self): -+ """has_master is True when /sys/net/devname/master exists.""" -+ self.assertFalse(net.has_master('enP1s1')) -+ ensure_file(os.path.join(self.sysdir, 'enP1s1', 'master')) -+ self.assertTrue(net.has_master('enP1s1')) -+ - def test_is_vlan(self): - """is_vlan is True when /sys/net/devname/uevent has DEVTYPE=vlan.""" - ensure_file(os.path.join(self.sysdir, 'eth0', 'uevent')) -@@ -364,6 +370,17 @@ class TestGetInterfaceMAC(CiTestCase): - expected = [('eth2', 'aa:bb:cc:aa:bb:cc', None, None)] - self.assertEqual(expected, net.get_interfaces()) - -+ def test_get_interfaces_by_mac_skips_master_devs(self): -+ """Ignore interfaces with a master device which would have dup mac.""" -+ mac1 = mac2 = 'aa:bb:cc:aa:bb:cc' -+ write_file(os.path.join(self.sysdir, 'eth1', 'addr_assign_type'), '0') -+ write_file(os.path.join(self.sysdir, 'eth1', 'address'), mac1) -+ write_file(os.path.join(self.sysdir, 'eth1', 'master'), "blah") -+ write_file(os.path.join(self.sysdir, 'eth2', 'addr_assign_type'), '0') -+ write_file(os.path.join(self.sysdir, 'eth2', 'address'), mac2) -+ expected = [('eth2', mac2, None, None)] -+ self.assertEqual(expected, net.get_interfaces()) -+ - - class TestInterfaceHasOwnMAC(CiTestCase): - --- -1.8.3.1 - diff --git a/SOURCES/ci-net-append-type-dhcp-46-only-if-dhcp-46-is-True-in-v.patch b/SOURCES/ci-net-append-type-dhcp-46-only-if-dhcp-46-is-True-in-v.patch deleted file mode 100644 index c92ed41..0000000 --- a/SOURCES/ci-net-append-type-dhcp-46-only-if-dhcp-46-is-True-in-v.patch +++ /dev/null @@ -1,139 +0,0 @@ -From 26a150ec6af91ae3ed5053069aa0c08d7064800f Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 4 May 2020 12:39:52 +0200 -Subject: [PATCH 1/6] net: append type:dhcp[46] only if dhcp[46] is True in v2 - netconfig - -RH-Author: Eduardo Otubo -Message-id: <20200327152826.13343-2-otubo@redhat.com> -Patchwork-id: 94459 -O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 1/6] net: append type:dhcp[46] only if dhcp[46] is True in v2 netconfig -Bugzilla: 1811753 -RH-Acked-by: Cathy Avery -RH-Acked-by: Vitaly Kuznetsov - -commit bd35300ba36bd63686715fa9661516a518781f6d -Author: Kurt Stieger -Date: Mon Mar 4 15:54:25 2019 +0000 - - net: append type:dhcp[46] only if dhcp[46] is True in v2 netconfig - - When providing netplan configuration to cloud-init, the internal - network state would enable DHCP if the 'dhcp' key was present in - the source config. In netplan, dhcp[46] is a boolean and the - value of the boolean should control whether DHCP is enabled rather - than the presence of the key. This issue leaded to inconsistant - sysconfig/network-scripts on fedora. 'BOOTPROTO' was always 'dhcp', - even if the address config was static. - - After this change a dhcp subnet is added only if the 'dhcp' setting - in source cfg dict is True. - - LP: #1818032 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/network_state.py | 4 +-- - tests/unittests/test_net.py | 62 ++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 64 insertions(+), 2 deletions(-) - -diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py -index f76e508..539b76d 100644 ---- a/cloudinit/net/network_state.py -+++ b/cloudinit/net/network_state.py -@@ -706,9 +706,9 @@ class NetworkStateInterpreter(object): - """Common ipconfig extraction from v2 to v1 subnets array.""" - - subnets = [] -- if 'dhcp4' in cfg: -+ if cfg.get('dhcp4'): - subnets.append({'type': 'dhcp4'}) -- if 'dhcp6' in cfg: -+ if cfg.get('dhcp6'): - self.use_ipv6 = True - subnets.append({'type': 'dhcp6'}) - -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 012c43b..4224301 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -103,6 +103,24 @@ STATIC_EXPECTED_1 = { - 'address': '10.0.0.2'}], - } - -+NETPLAN_DHCP_FALSE = """ -+version: 2 -+ethernets: -+ ens3: -+ match: -+ macaddress: 52:54:00:ab:cd:ef -+ dhcp4: false -+ dhcp6: false -+ addresses: -+ - 192.168.42.100/24 -+ - 2001:db8::100/32 -+ gateway4: 192.168.42.1 -+ gateway6: 2001:db8::1 -+ nameservers: -+ search: [example.com] -+ addresses: [192.168.42.53, 1.1.1.1] -+""" -+ - # Examples (and expected outputs for various renderers). - OS_SAMPLES = [ - { -@@ -2146,6 +2164,50 @@ USERCTL=no - self._compare_files_to_expected(entry[self.expected_name], found) - self._assert_headers(found) - -+ def test_netplan_dhcp_false_disable_dhcp_in_state(self): -+ """netplan config with dhcp[46]: False should not add dhcp in state""" -+ net_config = yaml.load(NETPLAN_DHCP_FALSE) -+ ns = network_state.parse_net_config_data(net_config, -+ skip_broken=False) -+ -+ dhcp_found = [snet for iface in ns.iter_interfaces() -+ for snet in iface['subnets'] if 'dhcp' in snet['type']] -+ -+ self.assertEqual([], dhcp_found) -+ -+ def test_netplan_dhcp_false_no_dhcp_in_sysconfig(self): -+ """netplan cfg with dhcp[46]: False should not have bootproto=dhcp""" -+ -+ entry = { -+ 'yaml': NETPLAN_DHCP_FALSE, -+ 'expected_sysconfig': { -+ 'ifcfg-ens3': textwrap.dedent("""\ -+ BOOTPROTO=none -+ DEFROUTE=yes -+ DEVICE=ens3 -+ DNS1=192.168.42.53 -+ DNS2=1.1.1.1 -+ DOMAIN=example.com -+ GATEWAY=192.168.42.1 -+ HWADDR=52:54:00:ab:cd:ef -+ IPADDR=192.168.42.100 -+ IPV6ADDR=2001:db8::100/32 -+ IPV6INIT=yes -+ IPV6_DEFAULTGW=2001:db8::1 -+ NETMASK=255.255.255.0 -+ NM_CONTROLLED=no -+ ONBOOT=yes -+ STARTMODE=auto -+ TYPE=Ethernet -+ USERCTL=no -+ """), -+ } -+ } -+ -+ found = self._render_and_read(network_config=yaml.load(entry['yaml'])) -+ self._compare_files_to_expected(entry['expected_sysconfig'], found) -+ self._assert_headers(found) -+ - - class TestOpenSuseSysConfigRendering(CiTestCase): - --- -1.8.3.1 - diff --git a/SOURCES/ci-net-handle-openstack-dhcpv6-stateless-configuration.patch b/SOURCES/ci-net-handle-openstack-dhcpv6-stateless-configuration.patch deleted file mode 100644 index 899127e..0000000 --- a/SOURCES/ci-net-handle-openstack-dhcpv6-stateless-configuration.patch +++ /dev/null @@ -1,270 +0,0 @@ -From 28e77dc7d641233b5793a159befc225fd3a8726b Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 4 May 2020 12:40:08 +0200 -Subject: [PATCH 5/6] net: handle openstack dhcpv6-stateless configuration -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -RH-Author: Eduardo Otubo -Message-id: <20200327152826.13343-6-otubo@redhat.com> -Patchwork-id: 94461 -O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 5/6] net: handle openstack dhcpv6-stateless configuration -Bugzilla: 1811753 -RH-Acked-by: Cathy Avery -RH-Acked-by: Vitaly Kuznetsov - -commit fac98983187c0984aa79c569c4b76cab90fd6f47 -Author: Harald JensÃ¥s -Date: Wed Oct 16 15:30:28 2019 +0000 - - net: handle openstack dhcpv6-stateless configuration - - Openstack subnets can be configured to use SLAAC by setting - ipv6_address_mode=dhcpv6-stateless. When this is the case - the sysconfig interface configuration should use - IPV6_AUTOCONF=yes and not set DHCPV6C=yes. - - This change sets the subnets type property to the full - network['type'] from openstack metadata. - - cloudinit/net/sysconfig.py and cloudinit/net/eni.py - are updated to support new subnet types: - - 'ipv6_dhcpv6-stateless' => IPV6_AUTOCONF=yes - - 'ipv6_dhcpv6-stateful' => DHCPV6C=yes - - Type 'dhcp6' in sysconfig is kept for backward compatibility - with any implementations that set subnet_type == 'dhcp6'. - - LP: #1847517 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/eni.py | 7 +- - cloudinit/net/sysconfig.py | 7 +- - cloudinit/sources/helpers/openstack.py | 3 +- - .../unittests/test_datasource/test_configdrive.py | 39 ++++++++++ - tests/unittests/test_net.py | 87 +++++++++++++++++++++- - 5 files changed, 139 insertions(+), 4 deletions(-) - -diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py -index 6423632..896a39b 100644 ---- a/cloudinit/net/eni.py -+++ b/cloudinit/net/eni.py -@@ -405,8 +405,13 @@ class Renderer(renderer.Renderer): - else: - ipv4_subnet_mtu = subnet.get('mtu') - iface['inet'] = subnet_inet -- if subnet['type'].startswith('dhcp'): -+ if (subnet['type'] == 'dhcp4' or subnet['type'] == 'dhcp6' or -+ subnet['type'] == 'ipv6_dhcpv6-stateful'): -+ # Configure network settings using DHCP or DHCPv6 - iface['mode'] = 'dhcp' -+ elif subnet['type'] == 'ipv6_dhcpv6-stateless': -+ # Configure network settings using SLAAC from RAs -+ iface['mode'] = 'auto' - - # do not emit multiple 'auto $IFACE' lines as older (precise) - # ifupdown complains -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index a4c7660..8b11dbb 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -328,10 +328,15 @@ class Renderer(renderer.Renderer): - for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): - mtu_key = 'MTU' - subnet_type = subnet.get('type') -- if subnet_type == 'dhcp6': -+ if subnet_type == 'dhcp6' or subnet_type == 'ipv6_dhcpv6-stateful': - # TODO need to set BOOTPROTO to dhcp6 on SUSE - iface_cfg['IPV6INIT'] = True -+ # Configure network settings using DHCPv6 - iface_cfg['DHCPV6C'] = True -+ elif subnet_type == 'ipv6_dhcpv6-stateless': -+ iface_cfg['IPV6INIT'] = True -+ # Configure network settings using SLAAC from RAs -+ iface_cfg['IPV6_AUTOCONF'] = True - elif subnet_type in ['dhcp4', 'dhcp']: - iface_cfg['BOOTPROTO'] = 'dhcp' - elif subnet_type == 'static': -diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py -index 9c29cea..9f2fd2d 100644 ---- a/cloudinit/sources/helpers/openstack.py -+++ b/cloudinit/sources/helpers/openstack.py -@@ -585,7 +585,8 @@ def convert_net_json(network_json=None, known_macs=None): - subnet = dict((k, v) for k, v in network.items() - if k in valid_keys['subnet']) - if 'dhcp' in network['type']: -- t = 'dhcp6' if network['type'].startswith('ipv6') else 'dhcp4' -+ t = (network['type'] if network['type'].startswith('ipv6') -+ else 'dhcp4') - subnet.update({ - 'type': t, - }) -diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py -index dcdabea..ed4e9d5 100644 ---- a/tests/unittests/test_datasource/test_configdrive.py -+++ b/tests/unittests/test_datasource/test_configdrive.py -@@ -503,6 +503,45 @@ class TestNetJson(CiTestCase): - known_macs=KNOWN_MACS) - self.assertEqual(myds.network_config, network_config) - -+ def test_network_config_conversion_dhcp6(self): -+ """Test some ipv6 input network json and check the expected -+ conversions.""" -+ in_data = { -+ 'links': [ -+ {'vif_id': '2ecc7709-b3f7-4448-9580-e1ec32d75bbd', -+ 'ethernet_mac_address': 'fa:16:3e:69:b0:58', -+ 'type': 'ovs', 'mtu': None, 'id': 'tap2ecc7709-b3'}, -+ {'vif_id': '2f88d109-5b57-40e6-af32-2472df09dc33', -+ 'ethernet_mac_address': 'fa:16:3e:d4:57:ad', -+ 'type': 'ovs', 'mtu': None, 'id': 'tap2f88d109-5b'}, -+ ], -+ 'networks': [ -+ {'link': 'tap2ecc7709-b3', 'type': 'ipv6_dhcpv6-stateless', -+ 'network_id': '6d6357ac-0f70-4afa-8bd7-c274cc4ea235', -+ 'id': 'network0'}, -+ {'link': 'tap2f88d109-5b', 'type': 'ipv6_dhcpv6-stateful', -+ 'network_id': 'd227a9b3-6960-4d94-8976-ee5788b44f54', -+ 'id': 'network1'}, -+ ] -+ } -+ out_data = { -+ 'version': 1, -+ 'config': [ -+ {'mac_address': 'fa:16:3e:69:b0:58', -+ 'mtu': None, -+ 'name': 'enp0s1', -+ 'subnets': [{'type': 'ipv6_dhcpv6-stateless'}], -+ 'type': 'physical'}, -+ {'mac_address': 'fa:16:3e:d4:57:ad', -+ 'mtu': None, -+ 'name': 'enp0s2', -+ 'subnets': [{'type': 'ipv6_dhcpv6-stateful'}], -+ 'type': 'physical'} -+ ], -+ } -+ conv_data = openstack.convert_net_json(in_data, known_macs=KNOWN_MACS) -+ self.assertEqual(out_data, conv_data) -+ - def test_network_config_conversions(self): - """Tests a bunch of input network json and checks the - expected conversions.""" -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index df5658d..70d13f3 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -712,6 +712,82 @@ NETWORK_CONFIGS = { - """), - }, - }, -+ 'dhcpv6_stateless': { -+ 'expected_eni': textwrap.dedent("""\ -+ auto lo -+ iface lo inet loopback -+ -+ auto iface0 -+ iface iface0 inet6 auto -+ """).rstrip(' '), -+ 'expected_netplan': textwrap.dedent(""" -+ network: -+ version: 2 -+ ethernets: -+ iface0: -+ dhcp6: true -+ """).rstrip(' '), -+ 'yaml': textwrap.dedent("""\ -+ version: 1 -+ config: -+ - type: 'physical' -+ name: 'iface0' -+ subnets: -+ - {'type': 'ipv6_dhcpv6-stateless'} -+ """).rstrip(' '), -+ 'expected_sysconfig': { -+ 'ifcfg-iface0': textwrap.dedent("""\ -+ BOOTPROTO=none -+ DEVICE=iface0 -+ IPV6_AUTOCONF=yes -+ IPV6INIT=yes -+ DEVICE=iface0 -+ NM_CONTROLLED=no -+ ONBOOT=yes -+ STARTMODE=auto -+ TYPE=Ethernet -+ USERCTL=no -+ """), -+ }, -+ }, -+ 'dhcpv6_stateful': { -+ 'expected_eni': textwrap.dedent("""\ -+ auto lo -+ iface lo inet loopback -+ -+ auto iface0 -+ iface iface0 inet6 dhcp -+ """).rstrip(' '), -+ 'expected_netplan': textwrap.dedent(""" -+ network: -+ version: 2 -+ ethernets: -+ iface0: -+ dhcp6: true -+ """).rstrip(' '), -+ 'yaml': textwrap.dedent("""\ -+ version: 1 -+ config: -+ - type: 'physical' -+ name: 'iface0' -+ subnets: -+ - {'type': 'ipv6_dhcpv6-stateful'} -+ """).rstrip(' '), -+ 'expected_sysconfig': { -+ 'ifcfg-iface0': textwrap.dedent("""\ -+ BOOTPROTO=none -+ DEVICE=iface0 -+ DHCPV6C=yes -+ IPV6INIT=yes -+ DEVICE=iface0 -+ NM_CONTROLLED=no -+ ONBOOT=yes -+ STARTMODE=auto -+ TYPE=Ethernet -+ USERCTL=no -+ """), -+ }, -+ }, - 'all': { - 'expected_eni': ("""\ - auto lo -@@ -2300,8 +2376,11 @@ USERCTL=no - } - } - -+ -+ def test_dhcpv6_stateless_config(self): -+ entry = NETWORK_CONFIGS['dhcpv6_stateless'] - found = self._render_and_read(network_config=yaml.load(entry['yaml'])) -- self._compare_files_to_expected(entry['expected_sysconfig'], found) -+ self._compare_files_to_expected(entry[self.expected_name], found) - self._assert_headers(found) - - def test_from_v2_route_metric(self): -@@ -2334,6 +2413,12 @@ USERCTL=no - self._compare_files_to_expected( - expected, self._render_and_read(network_config=v2data)) - -+ def test_dhcpv6_stateful_config(self): -+ entry = NETWORK_CONFIGS['dhcpv6_stateful'] -+ found = self._render_and_read(network_config=yaml.load(entry['yaml'])) -+ self._compare_files_to_expected(entry[self.expected_name], found) -+ self._assert_headers(found) -+ - - class TestOpenSuseSysConfigRendering(CiTestCase): - --- -1.8.3.1 - diff --git a/SOURCES/ci-net-skip-bond-interfaces-in-get_interfaces.patch b/SOURCES/ci-net-skip-bond-interfaces-in-get_interfaces.patch deleted file mode 100644 index c193ee9..0000000 --- a/SOURCES/ci-net-skip-bond-interfaces-in-get_interfaces.patch +++ /dev/null @@ -1,45 +0,0 @@ -From bb7f0106264091ea4e0753f2a5c1276e9020045c Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 19 Feb 2020 13:32:55 +0100 -Subject: [PATCH 2/3] net: skip bond interfaces in get_interfaces - -RH-Author: Eduardo Otubo -Message-id: <20200219133256.9544-2-otubo@redhat.com> -Patchwork-id: 93936 -O-Subject: [RHEL-7.9 cloud-init PATCHv2 1/2] net: skip bond interfaces in get_interfaces -Bugzilla: 1768770 -RH-Acked-by: Mohammed Gamal -RH-Acked-by: Vitaly Kuznetsov - -commit e5f542132568a8da63823f478cea52bfb28ac655 -Author: Stanislav Makar -Date: Mon Jul 15 20:12:31 2019 +0000 - - net: skip bond interfaces in get_interfaces - - bonds may inherit mac address from a physical interface - - LP: #1812857 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/__init__.py | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py -index 3642fb1..e758006 100644 ---- a/cloudinit/net/__init__.py -+++ b/cloudinit/net/__init__.py -@@ -622,6 +622,8 @@ def get_interfaces(): - continue - if is_vlan(name): - continue -+ if is_bond(name): -+ continue - mac = get_interface_mac(name) - # some devices may not have a mac (tun0) - if not mac: --- -1.8.3.1 - diff --git a/SOURCES/ci-net-sysconfig-Handle-default-route-setup-for-dhcp-co.patch b/SOURCES/ci-net-sysconfig-Handle-default-route-setup-for-dhcp-co.patch deleted file mode 100644 index 31f7b35..0000000 --- a/SOURCES/ci-net-sysconfig-Handle-default-route-setup-for-dhcp-co.patch +++ /dev/null @@ -1,311 +0,0 @@ -From a22a059e36ec56d0d6d7e2a63ccff56d6c19f9d6 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Mon, 4 May 2020 12:39:55 +0200 -Subject: [PATCH 2/6] net/sysconfig: Handle default route setup for dhcp - configured NICs - -RH-Author: Eduardo Otubo -Message-id: <20200327152826.13343-3-otubo@redhat.com> -Patchwork-id: 94457 -O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 2/6] net/sysconfig: Handle default route setup for dhcp configured NICs -Bugzilla: 1811753 -RH-Acked-by: Cathy Avery -RH-Acked-by: Vitaly Kuznetsov - -commit 3acaacc92be1b7d7bad099c323d6e923664a8afa -Author: Robert Schweikert -Date: Tue Mar 12 21:08:22 2019 +0000 - - net/sysconfig: Handle default route setup for dhcp configured NICs - - When the network configuration has a default route configured and - another network device that is configured with dhcp, SUSE sysconfig - output should not accept the default route provided by the dhcp - server. - - LP: #1812117 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/net/network_state.py | 41 +++++++++++++++++++++------ - cloudinit/net/sysconfig.py | 31 +++++++++++++++------ - tests/unittests/test_net.py | 63 ++++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 118 insertions(+), 17 deletions(-) - -diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py -index 539b76d..4d19f56 100644 ---- a/cloudinit/net/network_state.py -+++ b/cloudinit/net/network_state.py -@@ -148,6 +148,7 @@ class NetworkState(object): - self._network_state = copy.deepcopy(network_state) - self._version = version - self.use_ipv6 = network_state.get('use_ipv6', False) -+ self._has_default_route = None - - @property - def config(self): -@@ -157,14 +158,6 @@ class NetworkState(object): - def version(self): - return self._version - -- def iter_routes(self, filter_func=None): -- for route in self._network_state.get('routes', []): -- if filter_func is not None: -- if filter_func(route): -- yield route -- else: -- yield route -- - @property - def dns_nameservers(self): - try: -@@ -179,6 +172,12 @@ class NetworkState(object): - except KeyError: - return [] - -+ @property -+ def has_default_route(self): -+ if self._has_default_route is None: -+ self._has_default_route = self._maybe_has_default_route() -+ return self._has_default_route -+ - def iter_interfaces(self, filter_func=None): - ifaces = self._network_state.get('interfaces', {}) - for iface in six.itervalues(ifaces): -@@ -188,6 +187,32 @@ class NetworkState(object): - if filter_func(iface): - yield iface - -+ def iter_routes(self, filter_func=None): -+ for route in self._network_state.get('routes', []): -+ if filter_func is not None: -+ if filter_func(route): -+ yield route -+ else: -+ yield route -+ -+ def _maybe_has_default_route(self): -+ for route in self.iter_routes(): -+ if self._is_default_route(route): -+ return True -+ for iface in self.iter_interfaces(): -+ for subnet in iface.get('subnets', []): -+ for route in subnet.get('routes', []): -+ if self._is_default_route(route): -+ return True -+ return False -+ -+ def _is_default_route(self, route): -+ default_nets = ('::', '0.0.0.0') -+ return ( -+ route.get('prefix') == 0 -+ and route.get('network') in default_nets -+ ) -+ - - @six.add_metaclass(CommandHandlerMeta) - class NetworkStateInterpreter(object): -diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py -index 52bb848..5c1b4eb 100644 ---- a/cloudinit/net/sysconfig.py -+++ b/cloudinit/net/sysconfig.py -@@ -320,7 +320,7 @@ class Renderer(renderer.Renderer): - iface_cfg[new_key] = old_value - - @classmethod -- def _render_subnets(cls, iface_cfg, subnets): -+ def _render_subnets(cls, iface_cfg, subnets, has_default_route): - # setting base values - iface_cfg['BOOTPROTO'] = 'none' - -@@ -329,6 +329,7 @@ class Renderer(renderer.Renderer): - mtu_key = 'MTU' - subnet_type = subnet.get('type') - if subnet_type == 'dhcp6': -+ # TODO need to set BOOTPROTO to dhcp6 on SUSE - iface_cfg['IPV6INIT'] = True - iface_cfg['DHCPV6C'] = True - elif subnet_type in ['dhcp4', 'dhcp']: -@@ -372,9 +373,9 @@ class Renderer(renderer.Renderer): - ipv6_index = -1 - for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): - subnet_type = subnet.get('type') -- if subnet_type == 'dhcp6': -- continue -- elif subnet_type in ['dhcp4', 'dhcp']: -+ if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']: -+ if has_default_route and iface_cfg['BOOTPROTO'] != 'none': -+ iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False - continue - elif subnet_type == 'static': - if subnet_is_ipv6(subnet): -@@ -440,6 +441,8 @@ class Renderer(renderer.Renderer): - # TODO(harlowja): add validation that no other iface has - # also provided the default route? - iface_cfg['DEFROUTE'] = True -+ if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4', 'dhcp6'): -+ iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = True - if 'gateway' in route: - if is_ipv6 or is_ipv6_addr(route['gateway']): - iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] -@@ -490,7 +493,9 @@ class Renderer(renderer.Renderer): - iface_cfg = iface_contents[iface_name] - route_cfg = iface_cfg.routes - -- cls._render_subnets(iface_cfg, iface_subnets) -+ cls._render_subnets( -+ iface_cfg, iface_subnets, network_state.has_default_route -+ ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) - - @classmethod -@@ -515,7 +520,9 @@ class Renderer(renderer.Renderer): - - iface_subnets = iface.get("subnets", []) - route_cfg = iface_cfg.routes -- cls._render_subnets(iface_cfg, iface_subnets) -+ cls._render_subnets( -+ iface_cfg, iface_subnets, network_state.has_default_route -+ ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) - - # iter_interfaces on network-state is not sorted to produce -@@ -544,7 +551,9 @@ class Renderer(renderer.Renderer): - - iface_subnets = iface.get("subnets", []) - route_cfg = iface_cfg.routes -- cls._render_subnets(iface_cfg, iface_subnets) -+ cls._render_subnets( -+ iface_cfg, iface_subnets, network_state.has_default_route -+ ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) - - @staticmethod -@@ -603,7 +612,9 @@ class Renderer(renderer.Renderer): - - iface_subnets = iface.get("subnets", []) - route_cfg = iface_cfg.routes -- cls._render_subnets(iface_cfg, iface_subnets) -+ cls._render_subnets( -+ iface_cfg, iface_subnets, network_state.has_default_route -+ ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) - - @classmethod -@@ -615,7 +626,9 @@ class Renderer(renderer.Renderer): - iface_cfg.kind = 'infiniband' - iface_subnets = iface.get("subnets", []) - route_cfg = iface_cfg.routes -- cls._render_subnets(iface_cfg, iface_subnets) -+ cls._render_subnets( -+ iface_cfg, iface_subnets, network_state.has_default_route -+ ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) - - @classmethod -diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py -index 4224301..a975678 100644 ---- a/tests/unittests/test_net.py -+++ b/tests/unittests/test_net.py -@@ -546,6 +546,7 @@ NETWORK_CONFIGS = { - BOOTPROTO=dhcp - DEFROUTE=yes - DEVICE=eth99 -+ DHCLIENT_SET_DEFAULT_ROUTE=yes - DNS1=8.8.8.8 - DNS2=8.8.4.4 - DOMAIN="barley.maas sach.maas" -@@ -913,6 +914,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - 'ifcfg-bond0.200': textwrap.dedent("""\ - BOOTPROTO=dhcp - DEVICE=bond0.200 -+ DHCLIENT_SET_DEFAULT_ROUTE=no - ONBOOT=yes - PHYSDEV=bond0 - TYPE=Ethernet -@@ -996,6 +998,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true - 'ifcfg-eth5': textwrap.dedent("""\ - BOOTPROTO=dhcp - DEVICE=eth5 -+ DHCLIENT_SET_DEFAULT_ROUTE=no - HWADDR=98:bb:9f:2c:e8:8a - ONBOOT=no - TYPE=Ethernet -@@ -1624,6 +1627,23 @@ CONFIG_V1_SIMPLE_SUBNET = { - 'type': 'static'}], - 'type': 'physical'}]} - -+CONFIG_V1_MULTI_IFACE = { -+ 'version': 1, -+ 'config': [{'type': 'physical', -+ 'mtu': 1500, -+ 'subnets': [{'type': 'static', -+ 'netmask': '255.255.240.0', -+ 'routes': [{'netmask': '0.0.0.0', -+ 'network': '0.0.0.0', -+ 'gateway': '51.68.80.1'}], -+ 'address': '51.68.89.122', -+ 'ipv4': True}], -+ 'mac_address': 'fa:16:3e:25:b4:59', -+ 'name': 'eth0'}, -+ {'type': 'physical', -+ 'mtu': 9000, -+ 'subnets': [{'type': 'dhcp4'}], -+ 'mac_address': 'fa:16:3e:b1:ca:29', 'name': 'eth1'}]} - - DEFAULT_DEV_ATTRS = { - 'eth1000': { -@@ -2088,6 +2108,49 @@ USERCTL=no - """ - self.assertEqual(expected, found[nspath + 'ifcfg-interface0']) - -+ def test_network_config_v1_multi_iface_samples(self): -+ ns = network_state.parse_net_config_data(CONFIG_V1_MULTI_IFACE) -+ render_dir = self.tmp_path("render") -+ os.makedirs(render_dir) -+ renderer = self._get_renderer() -+ renderer.render_network_state(ns, target=render_dir) -+ found = dir2dict(render_dir) -+ nspath = '/etc/sysconfig/network-scripts/' -+ self.assertNotIn(nspath + 'ifcfg-lo', found.keys()) -+ expected_i1 = """\ -+# Created by cloud-init on instance boot automatically, do not edit. -+# -+BOOTPROTO=none -+DEFROUTE=yes -+DEVICE=eth0 -+GATEWAY=51.68.80.1 -+HWADDR=fa:16:3e:25:b4:59 -+IPADDR=51.68.89.122 -+MTU=1500 -+NETMASK=255.255.240.0 -+NM_CONTROLLED=no -+ONBOOT=yes -+STARTMODE=auto -+TYPE=Ethernet -+USERCTL=no -+""" -+ self.assertEqual(expected_i1, found[nspath + 'ifcfg-eth0']) -+ expected_i2 = """\ -+# Created by cloud-init on instance boot automatically, do not edit. -+# -+BOOTPROTO=dhcp -+DEVICE=eth1 -+DHCLIENT_SET_DEFAULT_ROUTE=no -+HWADDR=fa:16:3e:b1:ca:29 -+MTU=9000 -+NM_CONTROLLED=no -+ONBOOT=yes -+STARTMODE=auto -+TYPE=Ethernet -+USERCTL=no -+""" -+ self.assertEqual(expected_i2, found[nspath + 'ifcfg-eth1']) -+ - def test_config_with_explicit_loopback(self): - ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK) - render_dir = self.tmp_path("render") --- -1.8.3.1 - diff --git a/SOURCES/ci-util-json.dumps-on-python-2.7-will-handle-UnicodeDec.patch b/SOURCES/ci-util-json.dumps-on-python-2.7-will-handle-UnicodeDec.patch deleted file mode 100644 index d9cdbcc..0000000 --- a/SOURCES/ci-util-json.dumps-on-python-2.7-will-handle-UnicodeDec.patch +++ /dev/null @@ -1,144 +0,0 @@ -From 4f1c3f5be0306da485135544ced4a676753a9373 Mon Sep 17 00:00:00 2001 -From: Eduardo Otubo -Date: Wed, 16 Oct 2019 12:10:24 +0200 -Subject: [PATCH 2/2] util: json.dumps on python 2.7 will handle - UnicodeDecodeError on binary - -RH-Author: Eduardo Otubo -Message-id: <20191016121024.23694-1-otubo@redhat.com> -Patchwork-id: 91812 -O-Subject: [RHEL-7.8/RHEL-8.1.0 cloud-init PATCH] util: json.dumps on python 2.7 will handle UnicodeDecodeError on binary -Bugzilla: 1744718 -RH-Acked-by: Vitaly Kuznetsov -RH-Acked-by: Mohammed Gamal - -commit 067516d7bc917e4921b9f1424b7a64e92cae0ad2 -Author: Chad Smith -Date: Fri Sep 27 20:46:00 2019 +0000 - - util: json.dumps on python 2.7 will handle UnicodeDecodeError on binary - - Since python 2.7 doesn't handle UnicodeDecodeErrors with the default - handler - - LP: #1801364 - -Signed-off-by: Eduardo Otubo -Signed-off-by: Miroslav Rezanina ---- - cloudinit/sources/tests/test_init.py | 12 +++++------- - cloudinit/tests/test_util.py | 20 ++++++++++++++++++++ - cloudinit/util.py | 27 +++++++++++++++++++++++++-- - 3 files changed, 50 insertions(+), 9 deletions(-) - -diff --git a/cloudinit/sources/tests/test_init.py b/cloudinit/sources/tests/test_init.py -index 6378e98..9698261 100644 ---- a/cloudinit/sources/tests/test_init.py -+++ b/cloudinit/sources/tests/test_init.py -@@ -457,19 +457,17 @@ class TestDataSource(CiTestCase): - instance_json['ds']['meta_data']) - - @skipIf(not six.PY2, "Only python2 hits UnicodeDecodeErrors on non-utf8") -- def test_non_utf8_encoding_logs_warning(self): -- """When non-utf-8 values exist in py2 instance-data is not written.""" -+ def test_non_utf8_encoding_gets_b64encoded(self): -+ """When non-utf-8 values exist in py2 instance-data is b64encoded.""" - tmp = self.tmp_dir() - datasource = DataSourceTestSubclassNet( - self.sys_cfg, self.distro, Paths({'run_dir': tmp}), - custom_metadata={'key1': 'val1', 'key2': {'key2.1': b'ab\xaadef'}}) - self.assertTrue(datasource.get_data()) - json_file = self.tmp_path(INSTANCE_JSON_FILE, tmp) -- self.assertFalse(os.path.exists(json_file)) -- self.assertIn( -- "WARNING: Error persisting instance-data.json: 'utf8' codec can't" -- " decode byte 0xaa in position 2: invalid start byte", -- self.logs.getvalue()) -+ instance_json = util.load_json(util.load_file(json_file)) -+ key21_value = instance_json['ds']['meta_data']['key2']['key2.1'] -+ self.assertEqual('ci-b64:' + util.b64e(b'ab\xaadef'), key21_value) - - def test_get_hostname_subclass_support(self): - """Validate get_hostname signature on all subclasses of DataSource.""" -diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py -index e3d2dba..f4f95e9 100644 ---- a/cloudinit/tests/test_util.py -+++ b/cloudinit/tests/test_util.py -@@ -2,7 +2,9 @@ - - """Tests for cloudinit.util""" - -+import base64 - import logging -+import json - import platform - - import cloudinit.util as util -@@ -528,6 +530,24 @@ class TestGetLinuxDistro(CiTestCase): - self.assertEqual(('foo', '1.1', 'aarch64'), dist) - - -+class TestJsonDumps(CiTestCase): -+ def test_is_str(self): -+ """json_dumps should return a string.""" -+ self.assertTrue(isinstance(util.json_dumps({'abc': '123'}), str)) -+ -+ def test_utf8(self): -+ smiley = '\\ud83d\\ude03' -+ self.assertEqual( -+ {'smiley': smiley}, -+ json.loads(util.json_dumps({'smiley': smiley}))) -+ -+ def test_non_utf8(self): -+ blob = b'\xba\x03Qx-#y\xea' -+ self.assertEqual( -+ {'blob': 'ci-b64:' + base64.b64encode(blob).decode('utf-8')}, -+ json.loads(util.json_dumps({'blob': blob}))) -+ -+ - @mock.patch('os.path.exists') - class TestIsLXD(CiTestCase): - -diff --git a/cloudinit/util.py b/cloudinit/util.py -index a84112a..2c9ac66 100644 ---- a/cloudinit/util.py -+++ b/cloudinit/util.py -@@ -1590,10 +1590,33 @@ def json_serialize_default(_obj): - return 'Warning: redacted unserializable type {0}'.format(type(_obj)) - - -+def json_preserialize_binary(data): -+ """Preserialize any discovered binary values to avoid json.dumps issues. -+ -+ Used only on python 2.7 where default type handling is not honored for -+ failure to encode binary data. LP: #1801364. -+ TODO(Drop this function when py2.7 support is dropped from cloud-init) -+ """ -+ data = obj_copy.deepcopy(data) -+ for key, value in data.items(): -+ if isinstance(value, (dict)): -+ data[key] = json_preserialize_binary(value) -+ if isinstance(value, bytes): -+ data[key] = 'ci-b64:{0}'.format(b64e(value)) -+ return data -+ -+ - def json_dumps(data): - """Return data in nicely formatted json.""" -- return json.dumps(data, indent=1, sort_keys=True, -- separators=(',', ': '), default=json_serialize_default) -+ try: -+ return json.dumps( -+ data, indent=1, sort_keys=True, separators=(',', ': '), -+ default=json_serialize_default) -+ except UnicodeDecodeError: -+ if sys.version_info[:2] == (2, 7): -+ data = json_preserialize_binary(data) -+ return json.dumps(data) -+ raise - - - def yaml_dumps(obj, explicit_start=True, explicit_end=True): --- -1.8.3.1 - diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec index 02943a0..c933ba7 100644 --- a/SPECS/cloud-init.spec +++ b/SPECS/cloud-init.spec @@ -5,8 +5,8 @@ %global debug_package %{nil} Name: cloud-init -Version: 18.5 -Release: 12%{?dist}.2 +Version: 19.4 +Release: 1%{?dist}.7 Summary: Cloud instance init scripts Group: System Environment/Base @@ -18,59 +18,22 @@ Source1: cloud-init-tmpfiles.conf Patch0001: 0001-Add-initial-redhat-setup.patch Patch0002: 0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch Patch0003: 0003-limit-permissions-on-def_log_file.patch -Patch0004: 0004-azure-ensure-that-networkmanager-hook-script-runs.patch -Patch0005: 0005-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch -Patch0006: 0006-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch -Patch0007: 0007-sysconfig-Don-t-disable-IPV6_AUTOCONF.patch -Patch0008: 0008-net-Make-sysconfig-renderer-compatible-with-Network-.patch -Patch0009: 0009-net-Wait-for-dhclient-to-daemonize-before-reading-le.patch -Patch0010: 0010-cloud-init-per-don-t-use-dashes-in-sem-names.patch -Patch0011: 0011-azure-Filter-list-of-ssh-keys-pulled-from-fabric.patch -Patch0012: 0012-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch -# For bz#1691986 - [Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure -Patch13: ci-Azure-Ensure-platform-random_seed-is-always-serializ.patch -# For bz#1691986 - [Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure -Patch14: ci-DatasourceAzure-add-additional-logging-for-azure-dat.patch -# For bz#1691986 - [Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure -Patch15: ci-Azure-Changes-to-the-Hyper-V-KVP-Reporter.patch -# For bz#1691986 - [Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure -Patch16: ci-DataSourceAzure-Adjust-timeout-for-polling-IMDS.patch -# For bz#1691986 - [Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure -Patch17: ci-cc_mounts-check-if-mount-a-on-no-change-fstab-path.patch -# For bz#1692914 - [8.1] [WALA][cloud] cloud-init dhclient-hook script has some unexpected side-effects on Azure -Patch18: ci-Revert-azure-ensure-that-networkmanager-hook-script-.patch -# For bz#1691986 - [Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure -Patch19: ci-Azure-Return-static-fallback-address-as-if-failed-to.patch -# For bz#1706482 - [cloud-init][RHVM]cloud-init network configuration does not persist reboot [RHEL 8.2.0] -Patch20: ci-Fix-for-network-configuration-not-persisting-after-r.patch -# For bz#1744718 - [cloud-init][RHEL8][OpenStack] cloud-init can't persist instance-data.json -Patch21: ci-util-json.dumps-on-python-2.7-will-handle-UnicodeDec.patch -# For bz#1785648 - [RHEL8]cloud-user added to wheel group and sudoers.d causes 'sudo -v' prompts for passphrase -Patch22: ci-Removing-cloud-user-from-wheel.patch -# For bz#1802140 - [cloud-init][RHEL8.2]cloud-init cloud-final.service fail with KeyError: 'modules-init' after upgrade to version 18.2-1.el7_6.1 in RHV -Patch23: ci-cmd-main.py-Fix-missing-modules-init-key-in-modes-di.patch -# For bz#1788684 - [RHEL-8] cloud-init Azure byte swap (hyperV Gen2 Only) -Patch24: ci-azure-avoid-re-running-cloud-init-when-instance-id-i.patch -# For bz#1768770 - cloud-init complaining about enslaved mac -Patch25: ci-net-skip-bond-interfaces-in-get_interfaces.patch -# For bz#1768770 - cloud-init complaining about enslaved mac -Patch26: ci-net-add-is_master-check-for-filtering-device-list.patch -# For bz#1807797 - [cloud-init][RHEL-8.2.0] /etc/resolv.conf lose config after reboot (initial instance is ok) -Patch27: ci-Remove-race-condition-between-cloud-init-and-Network.patch -# For bz#1826262 - [RHEL8.2] Race condition of starting cloud-init and NetworkManager [rhel-8.2.0.z] -Patch28: ci-Make-cloud-init.service-execute-after-network-is-up.patch -# For bz#1811753 - [RHEL8] DHCP Stateful - Openstack DHCPv6 instance get no default gateway and is unable to send IPv6 traffic (Network unreachable errors). [rhel-8.2.0.z] -Patch29: ci-net-append-type-dhcp-46-only-if-dhcp-46-is-True-in-v.patch -# For bz#1811753 - [RHEL8] DHCP Stateful - Openstack DHCPv6 instance get no default gateway and is unable to send IPv6 traffic (Network unreachable errors). [rhel-8.2.0.z] -Patch30: ci-net-sysconfig-Handle-default-route-setup-for-dhcp-co.patch -# For bz#1811753 - [RHEL8] DHCP Stateful - Openstack DHCPv6 instance get no default gateway and is unable to send IPv6 traffic (Network unreachable errors). [rhel-8.2.0.z] -Patch31: ci-azure-net-generate_fallback_nic-emits-network-v2-con.patch -# For bz#1811753 - [RHEL8] DHCP Stateful - Openstack DHCPv6 instance get no default gateway and is unable to send IPv6 traffic (Network unreachable errors). [rhel-8.2.0.z] -Patch32: ci-azure-support-matching-dhcp-route-metrics-for-dual-s.patch -# For bz#1811753 - [RHEL8] DHCP Stateful - Openstack DHCPv6 instance get no default gateway and is unable to send IPv6 traffic (Network unreachable errors). [rhel-8.2.0.z] -Patch33: ci-net-handle-openstack-dhcpv6-stateless-configuration.patch -# For bz#1811753 - [RHEL8] DHCP Stateful - Openstack DHCPv6 instance get no default gateway and is unable to send IPv6 traffic (Network unreachable errors). [rhel-8.2.0.z] -Patch34: ci-net-IPv6-accept_ra-slaac-stateless-51.patch +Patch0004: 0004-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch +Patch0005: 0005-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch +Patch0006: 0006-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch +Patch0007: 0007-Remove-race-condition-between-cloud-init-and-Network.patch +# For bz#1831646 - [RHEL8.2.1] Race condition of starting cloud-init and NetworkManager +Patch8: ci-Make-cloud-init.service-execute-after-network-is-up.patch +# For bz#1844366 - [rhel-8.2.1]Incorrect ds-identify check in cloud-init-generator +Patch9: ci-Change-from-redhat-to-rhel-in-systemd-generator-tmpl.patch +# For bz#1844366 - [rhel-8.2.1]Incorrect ds-identify check in cloud-init-generator +Patch10: ci-cloud-init.service.tmpl-use-rhel-instead-of-redhat-4.patch +# For bz#1850456 - [RHEL8.2.1] Do not log IMDSv2 token values into cloud-init.log +Patch11: ci-ec2-Do-not-log-IMDSv2-token-values-instead-use-REDAC.patch +# For bz#1850456 - [RHEL8.2.1] Do not log IMDSv2 token values into cloud-init.log +Patch12: ci-ec2-only-redact-token-request-headers-in-logs-avoid-.patch +# For bz#1855595 - CVE-2018-10896 cloud-init: default configuration disabled deletion of SSH host keys [rhel-8] [rhel-8.2.1] +Patch13: ci-Enable-ssh_deletekeys-by-default.patch BuildArch: noarch @@ -166,10 +129,11 @@ mkdir -p $RPM_BUILD_ROOT%{_unitdir} cp rhel/systemd/* $RPM_BUILD_ROOT%{_unitdir}/ [ ! -d $RPM_BUILD_ROOT/usr/lib/systemd/system-generators ] && mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/system-generators -cp -p systemd/cloud-init-generator $RPM_BUILD_ROOT/usr/lib/systemd/system-generators +python3 tools/render-cloudcfg --variant rhel systemd/cloud-init-generator.tmpl > $RPM_BUILD_ROOT/usr/lib/systemd/system-generators/cloud-init-generator +chmod 755 $RPM_BUILD_ROOT/usr/lib/systemd/system-generators/cloud-init-generator [ ! -d $RPM_BUILD_ROOT/usr/lib/%{name} ] && mkdir -p $RPM_BUILD_ROOT/usr/lib/%{name} -cp -p tools/ds-identify $RPM_BUILD_ROOT/usr/lib/%{name}/ds-identify +cp -p tools/ds-identify $RPM_BUILD_ROOT%{_libexecdir}/%{name}/ds-identify %clean @@ -245,7 +209,7 @@ fi %{_udevrulesdir}/66-azure-ephemeral.rules %{_sysconfdir}/bash_completion.d/cloud-init %{_bindir}/cloud-id -/usr/lib/%{name}/ds-identify +%{_libexecdir}/%{name}/ds-identify /usr/lib/systemd/system-generators/cloud-init-generator @@ -253,20 +217,43 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog -* Mon May 04 2020 Miroslav Rezanina - 18.5-12.el8_2.2 -- ci-net-append-type-dhcp-46-only-if-dhcp-46-is-True-in-v.patch [bz#1811753] -- ci-net-sysconfig-Handle-default-route-setup-for-dhcp-co.patch [bz#1811753] -- ci-azure-net-generate_fallback_nic-emits-network-v2-con.patch [bz#1811753] -- ci-azure-support-matching-dhcp-route-metrics-for-dual-s.patch [bz#1811753] -- ci-net-handle-openstack-dhcpv6-stateless-configuration.patch [bz#1811753] -- ci-net-IPv6-accept_ra-slaac-stateless-51.patch [bz#1811753] -- Resolves: bz#1811753 - ([RHEL8] DHCP Stateful - Openstack DHCPv6 instance get no default gateway and is unable to send IPv6 traffic (Network unreachable errors). [rhel-8.2.0.z]) - -* Tue Apr 21 2020 Miroslav Rezanina - 18.5-12.el8_2.1 -- ci-Make-cloud-init.service-execute-after-network-is-up.patch [bz#1826262] -- Resolves: bz#1826262 - ([RHEL8.2] Race condition of starting cloud-init and NetworkManager [rhel-8.2.0.z]) +* Mon Jul 13 2020 Miroslav Rezanina - 19.4-1.el8.7 +- ci-Enable-ssh_deletekeys-by-default.patch [bz#1855595] +- Resolves: bz#1855595 + (CVE-2018-10896 cloud-init: default configuration disabled deletion of SSH host keys [rhel-8] [rhel-8.2.1]) + +* Fri Jun 26 2020 Miroslav Rezanina - 19.4-1.el8.6 +- Fixing cloud-init-generator permissions [bz#1844366] +- Resolves: bz#1844366 + ([rhel-8.2.1]Incorrect ds-identify check in cloud-init-generator) + +* Fri Jun 26 2020 Miroslav Rezanina - 19.4-1.el8.5 +- ci-ec2-Do-not-log-IMDSv2-token-values-instead-use-REDAC.patch [bz#1850456] +- ci-ec2-only-redact-token-request-headers-in-logs-avoid-.patch [bz#1850456] +- Resolves: bz#1850456 + ([RHEL8.2.1] Do not log IMDSv2 token values into cloud-init.log) + +* Wed Jun 24 2020 Miroslav Rezanina - 19.4-1.el8.4 +- ci-Render-the-generator-from-template-instead-of-cp.patch [bz#1844366] +- ci-Change-from-redhat-to-rhel-in-systemd-generator-tmpl.patch [bz#1844366] +- ci-cloud-init.service.tmpl-use-rhel-instead-of-redhat-4.patch [bz#1844366] +- Resolves: bz#1844366 + ([rhel-8.2.1]Incorrect ds-identify check in cloud-init-generator) + +* Tue Jun 09 2020 Miroslav Rezanina - 19.4-1.el8.3 +- ci-changing-ds-identify-patch-from-usr-lib-to-usr-libex.patch [bz#1844366] +- Resolves: bz#1844366 + ([rhel-8.2.1]Incorrect ds-identify check in cloud-init-generator) + +* Mon Jun 01 2020 Miroslav Rezanina - 19.4-1.el8.2 +- ci-Make-cloud-init.service-execute-after-network-is-up.patch [bz#1831646] +- Resolves: bz#1831646 + ([RHEL8.2.1] Race condition of starting cloud-init and NetworkManager) + +* Mon Apr 20 2020 Miroslav Rezanina - 19.4-1.el8.1 +- Rebase to cloud-init 19.4 [bz#1811912] +- Resolves: bz#1811912 + ([RHEL-8.2.1] cloud-init rebase to 19.4) * Tue Mar 10 2020 Miroslav Rezanina - 18.5-12.el8 - ci-Remove-race-condition-between-cloud-init-and-Network.patch [bz#1807797] @@ -313,11 +300,6 @@ fi - Resolves: bz#1692914 ([8.1] [WALA][cloud] cloud-init dhclient-hook script has some unexpected side-effects on Azure) -* Mon Jun 10 2019 Miroslav Rezanina - 18.5-5.el8 -- Improved gating testing [bz#1682786] -- Resolves: bz#1682786 - (cloud-init changes blocked until gating tests are added) - * Mon Jun 03 2019 Miroslav Rezanina - 18.5-4.el8 - ci-Azure-Ensure-platform-random_seed-is-always-serializ.patch [bz#1691986] - ci-DatasourceAzure-add-additional-logging-for-azure-dat.patch [bz#1691986] @@ -337,6 +319,7 @@ fi - Resolves: rhbz#1682786 (cloud-init changes blocked until gating tests are added) + * Wed Apr 10 2019 Danilo de Paula