Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for AIX #4713

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cloudinit/cmd/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,12 @@ def main_init(name, args):
LOG.debug("manual cache clean found from marker: %s", mfile)
existing = "trust"

init.purge_cache()
# rm_instance_lnk is now set to True. Else, cache will be used in case
# instead of optical disk to fetch network config. This check make sure
# that customer always uses latest config data even if the instance-id
# is matching
init.purge_cache(True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more comment:

This is a pretty big change to cloud-init. What justification do you have for it? Also, which datasources have you tested this on? Wouldn't this break any user using nocloud with only a CIDATA drive?



# Stage 5
bring_up_interfaces = _should_bring_up_interfaces(init, args)
Expand Down
2 changes: 1 addition & 1 deletion cloudinit/config/cc_disable_ec2_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from cloudinit.distros import ALL_DISTROS
from cloudinit.settings import PER_ALWAYS

REJECT_CMD_IF = ["route", "add", "-host", "169.254.169.254", "reject"]
REJECT_CMD_IF = ["route", "add", "-host", "169.254.169.254", "127.0.0.1", "reject"]
REJECT_CMD_IP = ["ip", "route", "add", "prohibit", "169.254.169.254"]

LOG = logging.getLogger(__name__)
Expand Down
4 changes: 4 additions & 0 deletions cloudinit/config/cc_growpart.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import os.path
import re
import stat
import platform
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than modify every single module to reject AIX, please extend util.py to extend system_info() and add an is_AIX() function

from abc import ABC, abstractmethod
from contextlib import suppress
from pathlib import Path
Expand Down Expand Up @@ -601,6 +602,9 @@ def resize_devices(resizer, devices):


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if platform.system().lower() == "aix":
return

if "growpart" not in cfg:
LOG.debug(
"No 'growpart' entry in cfg. Using default: %s", DEFAULT_CONFIG
Expand Down
3 changes: 3 additions & 0 deletions cloudinit/config/cc_grub_dpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ def is_efi_booted() -> bool:


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if _cloud.distro.name not in distros:
return

mycfg = cfg.get("grub_dpkg", cfg.get("grub-dpkg", {}))
if not mycfg:
mycfg = {}
Expand Down
5 changes: 5 additions & 0 deletions cloudinit/config/cc_keys_to_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import logging
import os
import platform
from textwrap import dedent

from cloudinit import subp, util
Expand All @@ -19,6 +20,7 @@
from cloudinit.settings import PER_INSTANCE

# This is a tool that cloud init provides
HELPER_TOOL_TPL_AIX = "/opt/freeware/lib/cloud-init/write-ssh-key-fingerprints"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no need to modify this, much less hard code it.
all you're doing is calling ./setup.py with different parameters

HELPER_TOOL_TPL = "%s/cloud-init/write-ssh-key-fingerprints"

distros = ["all"]
Expand Down Expand Up @@ -72,6 +74,9 @@


def _get_helper_tool_path(distro):
if platform.system().lower() == "aix":
return HELPER_TOOL_TPL_AIX

try:
base_lib = distro.usr_lib_exec
except AttributeError:
Expand Down
8 changes: 8 additions & 0 deletions cloudinit/config/cc_power_state_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import re
import subprocess
import time
import platform
from textwrap import dedent

from cloudinit import subp, util
Expand All @@ -24,6 +25,7 @@
frequency = PER_INSTANCE

EXIT_FAIL = 254
AIX = 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't use modifiable globals
once an @lru_cached util.is_AIX() this won't especially be needed


MODULE_DESCRIPTION = """\
This module handles shutdown/reboot after all config modules have been run. By
Expand Down Expand Up @@ -94,6 +96,9 @@ def givecmdline(pid):
line = output.splitlines()[1]
m = re.search(r"\d+ (\w|\.|-)+\s+(/\w.+)", line)
return m.group(2)
elif AIX:
(ps_out, _err) = subp.subp(["/usr/bin/ps", "-p", str(pid), "-oargs="], rcs=[0, 1])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless there is a ps in a standard path earlier than this, it shouldn't be necessary to give it the full path

return ps_out.strip()
else:
return util.load_file("/proc/%s/cmdline" % pid)
except IOError:
Expand Down Expand Up @@ -125,6 +130,9 @@ def check_condition(cond):


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if platform.system().lower() == "aix":
global AIX
AIX = 1
try:
(args, timeout, condition) = load_power_state(cfg, cloud.distro)
if args is None:
Expand Down
4 changes: 4 additions & 0 deletions cloudinit/config/cc_resizefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import logging
import os
import stat
import platform
from textwrap import dedent

from cloudinit import subp, util
Expand Down Expand Up @@ -235,6 +236,9 @@ def maybe_get_writable_device_path(devpath, info):


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if platform.system().lower() == "aix":
return

if len(args) != 0:
resize_root = args[0]
else:
Expand Down
11 changes: 10 additions & 1 deletion cloudinit/config/cc_resolv_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""Resolv Conf: configure resolv.conf"""

import logging
import platform
from textwrap import dedent

from cloudinit import templater, util
Expand Down Expand Up @@ -66,6 +67,7 @@
"opensuse-tumbleweed",
"photon",
"rhel",
"aix",
"sle_hpc",
"sle-micro",
"sles",
Expand Down Expand Up @@ -119,7 +121,14 @@ def generate_resolv_conf(template_fn, params, target_fname):

params["flags"] = flags
LOG.debug("Writing resolv.conf from template %s", template_fn)
templater.render_to_file(template_fn, target_fname, params)
if platform.system().lower() == "aix":
templater.render_to_file(template_fn, '/etc/resolv.conf', params)
else:
# Network Manager likes to overwrite the resolv.conf file, so make sure
# it is immutable after write
subp.subp(["chattr", "-i", target_fname])
templater.render_to_file(template_fn, target_fname, params)
subp.subp(["chattr", "+i", target_fname])


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
Expand Down
235 changes: 235 additions & 0 deletions cloudinit/config/cc_restore_volume_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# =================================================================
#
# (c) Copyright IBM Corp. 2015
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# =================================================================

import os
import re

from cloudinit.settings import PER_INSTANCE
from cloudinit import util, subp

frequency = PER_INSTANCE


IMPORTVG = "/usr/sbin/importvg"
LSPV = "/usr/sbin/lspv"
# an example of lspv output is shown below (where space = any whitespace):
# <disk_name> <physical_volume_id> <volume_group_name> <active>
LSVG = "/usr/sbin/lsvg"
MOUNT = "/usr/sbin/mount"

MAPPING_FILE = '/opt/freeware/etc/cloud/pvid_to_vg_mappings'


def handle(name, _cfg, _cloud, log, _args):
log.debug('Attempting to restore non-rootVG volume groups.')

if not os.access(MAPPING_FILE, os.R_OK):
log.warn('Physical volume ID to volume group mapping file "%s" does '
'not exist or permission to read the file does not exist. '
'Ensure that the file exists and the permissions for it are '
'correct.' % MAPPING_FILE)
return

with open(MAPPING_FILE, 'r') as f:
mapping_file_lines = f.readlines()

pvid_to_vg_map = parse_mapping_file(log, mapping_file_lines)

for physical_volume_id, volume_group_name in list(pvid_to_vg_map.items()):
if volume_group_name.lower() == 'none':
# skip any physical volumes that were not associated with any
# volume group on the captured system
log.warn('The physical volume with ID "%s" was associated '
'with a volume group labeled "%s" on the '
'captured system. This physical volume will not '
'be associated with a volume group.' %
(physical_volume_id, volume_group_name))
continue

# Run lspv for each captured physical volume ID, so that any
# changes caused by importvg are picked up the next time
# through the loop. The effect of this is that importvg is
# only run once per volume group: "The PhysicalVolume parameter
# specifies only one physical volume to identify the volume group; any
# remaining physical volumes (those belonging to the same volume group)
# are found the importvg command and included in the import".
pv = get_physical_volume_from_lspv(log, physical_volume_id)

if pv is None:
log.warn('The physical volume with ID "%s" was not found, so it '
'cannot be associated with volume group "%s".' %
(physical_volume_id, volume_group_name))
continue

if (pv['volume_group'] is None and not pv['active']):
# If the volume group is not set and the disk is not active,
# then set it.
set_volume_group(log, pv['hdisk'], volume_group_name)

# Ensure that all volume groups captured are now present
expected_volume_groups = set(vg for vg in list(pvid_to_vg_map.values()))
existing_volume_groups = get_existing_volume_groups(log)
for group in expected_volume_groups:
if group not in existing_volume_groups:
msg = 'Volume group "%s" is not present.' % group
log.error(msg)
raise Exception(msg)

# Ensure that all disks get mounted that are marked in /etc/filesystems as
# auto-mounting. These disks would normally get auto-mounted at system
# startup, but not after importvg. Note that some errors may appear for
# filesystems that are already mounted.
try:
out = subp.subp([MOUNT, "all"])[0]
log.debug(out)
except subp.ProcessExecutionError as e:
log.debug('Attempting to mount disks marked as auto-mounting resulted '
'in errors. This is likely due to attempting to mount '
'filesystems that are already mounted, therefore '
'ignoring: %s.' % e)

# Clean up the mapping file
os.remove(MAPPING_FILE)


def parse_mapping_file(log, lines):
'''
Parses the lines, skipping any blank lines, expecting the each line to be
a volume group name, whitespace, then a physical volume ID. E.g.:
<vol_group_name1> <physical_vol_id1>
<vol_group_name1> <physical_vol_id2>
<vol_group_name2> <physical_vol_id3>
<vol_group_name3> <physical_vol_id4>
Note that the order does not matter. Physical volume IDs that are "none"
and volume group names that are "rootvg" will raise exceptions as the
script generating the mapping file should not include those entries.

Returns a dictionary with keys of physical volume IDs mapping to their
corresponding volume group name.
'''
pvid_to_vg_map = {}

for line in lines:
if line.strip() == '':
continue

split_line = line.strip().split()
if len(split_line) != 2:
msg = ('Physical volume ID to volume group mapping file contains '
'lines in an invalid format. Each line should contain a '
'volume group name, a single space, then a physical volume '
'ID. Invalid line: "%s".' % line.strip())
log.error(msg)
raise Exception(msg)

volume_group_name, physical_volume_id = tuple(line.split())
if physical_volume_id.lower() == 'none':
msg = ('Physical volume ID parsed as "%s", but there should be no '
'entries in the mapping file like this.' %
physical_volume_id)
log.error(msg)
raise Exception(msg)
if volume_group_name.lower() == 'rootvg':
msg = ('Volume group name parsed as "%s", but there should be no '
'entries in the mapping file like this.' %
volume_group_name)
log.error(msg)
raise Exception(msg)

pvid_to_vg_map[physical_volume_id] = volume_group_name

return pvid_to_vg_map


def get_physical_volume_from_lspv(log, physical_volume_id):
'''
The output of the lspv command for a specific physical volume ID is
returned from this method as a dictionary representing the physical volume
ID. If the lspv command output does not contain any output corresponding
to the given physical volume ID, then None is returned.

The dictionary returned is of the following format:
{'hdisk': <hdisk_name>,
'physical_volume_id': <physical_vol_id>,
'volume_group': <vol_group>,
'active': <active, boolean>}
'''
try:
env = os.environ
env['LANG'] = 'C'
lspv_out = subp.subp([LSPV], env=env)[0].strip()
except subp.ProcessExecutionError:
util.logexc(log, 'Failed to run lspv command.')
raise

lspv_out_specific_pvid = re.findall(r'.*%s.*' % physical_volume_id,
lspv_out)
if len(lspv_out_specific_pvid) < 1:
return None

lspv_specific_pvid = lspv_out_specific_pvid[0].split()

if len(lspv_specific_pvid) < 3:
msg = ('Output from lspv does not match the expected format. The '
'expected output is of of the form "<disk_name> '
'<physical_volume_id> <volume_group_name> <active>". The '
'actual output was: "%s".' % lspv_out_specific_pvid)
log.error(msg)
raise Exception(msg)

volume_group = lspv_specific_pvid[2]
if volume_group.lower() == 'none':
volume_group = None

physical_volume = {
'hdisk': lspv_specific_pvid[0],
'physical_volume_id': lspv_specific_pvid[1],
'volume_group': volume_group,
'active': 'active' in lspv_specific_pvid
}

return physical_volume


def set_volume_group(log, hdisk, volume_group_name):
'''
Uses the importvg command to set the volume group for the given hdisk.
'''
try:
out = subp.subp([IMPORTVG, "-y", volume_group_name, hdisk])[0]
log.debug(out)
except subp.ProcessExecutionError:
util.logexc(log, 'Failed to set the volume group for disk '
'%s.' % hdisk)
raise


def get_existing_volume_groups(log):
'''
Uses the lsvg command to get all existing volume groups.
'''
volume_groups = []
try:
env = os.environ
env['LANG'] = 'C'
lsvg_out = subp.subp([LSVG], env=env)[0].strip()
volume_groups = lsvg_out.split('\n')
volume_groups = [vg.strip() for vg in volume_groups]
except subp.ProcessExecutionError:
util.logexc(log, 'Failed to run lsvg command.')
raise
return volume_groups
Loading
Loading