Skip to content

Commit

Permalink
automatically generate list of recent releases
Browse files Browse the repository at this point in the history
  • Loading branch information
bschimke95 committed Sep 20, 2024
1 parent 592f7cf commit 316f7cc
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 42 deletions.
8 changes: 2 additions & 6 deletions .github/workflows/integration-informing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,13 @@ jobs:
TEST_SUBSTRATE: lxd
TEST_LXD_IMAGE: ${{ matrix.os }}
TEST_INSPECTION_REPORTS_DIR: ${{ github.workspace }}/inspection-reports
# TODO(ben): Use candidate/stable
# -strict suffix is removed from the channel name in the run script
TEST_VERSION_UPGRADE_CHANNELS: |
1.30-${{ matrix.patch }}/edge
1.31-${{ matrix.patch }}/edge
# Test the latest (up to) 6 releases for the flavour
TEST_VERSION_UPGRADE_CHANNELS: "recent 6 ${{ matrix.patch }}"
run: |
# IPv6-only is only supported on moonray
if [[ "${{ matrix.patch }}" == "moonray" ]]; then
export TEST_IPV6_ONLY="true"
fi
export TEST_VERSION_UPGRADE_CHANNELS=${TEST_VERSION_UPGRADE_CHANNELS//-strict/}
cd tests/integration && sg lxd -c 'tox -e integration'
- name: Prepare inspection reports
if: failure()
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,8 @@ jobs:
TEST_SUBSTRATE: lxd
TEST_LXD_IMAGE: ${{ matrix.os }}
TEST_INSPECTION_REPORTS_DIR: ${{ github.workspace }}/inspection-reports
TEST_VERSION_UPGRADE_CHANNELS: |
# TODO(ben): Use candidate/stable
1.30-classic/edge
1.31-classic/edge
# Test the latest (up to) 6 releases
TEST_VERSION_UPGRADE_CHANNELS: "recent 6 classic"
run: |
cd tests/integration && sg lxd -c 'tox -e integration
- name: Prepare inspection reports
Expand Down
1 change: 1 addition & 0 deletions tests/integration/tests/test_util/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,5 @@

# A list of space-separated channels for which the upgrade tests should be run in sequential order.
# First entry is the bootstrap channel. Afterwards, upgrades are done in order.
# Alternatively, use 'recent <num> <flavour>' to get the latest <num> channels for <flavour>.
VERSION_UPGRADE_CHANNELS = os.environ.get("TEST_VERSION_UPGRADE_CHANNELS").strip().split()
84 changes: 84 additions & 0 deletions tests/integration/tests/test_util/snap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import json
import logging
import re
from typing import List
import urllib.error
import urllib.request

LOG = logging.getLogger(__name__)

SNAP_NAME = "k8s"

# For Snap Store API request
SNAPSTORE_API = "https://api.snapcraft.io/v2/snaps/info/"
SNAPSTORE_HEADERS = {
"Snap-Device-Series": "16",
"User-Agent": "Mozilla/5.0",
}
RISK_LEVELS = ["stable", "candidate", "beta", "edge"]


def get_snap_info(snap_name=SNAP_NAME):
"""Get the snap info from the Snap Store API."""
req = urllib.request.Request(SNAPSTORE_API + snap_name, headers=SNAPSTORE_HEADERS)
try:
with urllib.request.urlopen(req) as response: # nosec
return json.loads(response.read().decode())
except urllib.error.HTTPError as e:
LOG.exception("HTTPError ({%s}): {%s} {%s}", req.full_url, e.code, e.reason)
raise
except urllib.error.URLError as e:
LOG.exception("URLError ({%s}): {%s}", req.full_url, e.reason)
raise


def get_latest_channels(
num_of_channels: int, flavor: str, include_latest=True
) -> List[str]:
"""Get an ascending list of latest channels based on the number of channels and flavour.
e.g. get_latest_release_channels(3, "classic") -> ['1.31-classic/candidate', '1.30-classic/candidate']
if there are less than num_of_channels available, return all available channels.
Only the most stable risk level is returned for each major.minor version.
By default, the `latest/edge/<flavor>` channel is included in the list.
"""
snap_info = get_snap_info()

# Extract channel information
channels = snap_info.get("channel-map", [])
available_channels = [ch["channel"]["name"] for ch in channels]

# Define regex pattern to match channels in the format 'major.minor-flavour'
if flavor == "strict":
pattern = re.compile(r"(\d+)\.(\d+)\/(" + "|".join(RISK_LEVELS) + ")")
else:
pattern = re.compile(
r"(\d+)\.(\d+)-" + re.escape(flavor) + r"\/(" + "|".join(RISK_LEVELS) + ")"
)

# Dictionary to store the highest risk level for each major.minor
channel_map = {}

for channel in available_channels:
match = pattern.match(channel)
if match:
major, minor, risk = match.groups()
major_minor = (int(major), int(minor))

# Store only the highest risk level channel for each major.minor
if major_minor not in channel_map or RISK_LEVELS.index(
risk
) < RISK_LEVELS.index(channel_map[major_minor][1]):
channel_map[major_minor] = (channel, risk)

# Sort channels by major and minor version in descending order
sorted_channels = sorted(channel_map.keys(), reverse=False)

# Prepare final channel list
final_channels = [channel_map[mm][0] for mm in sorted_channels[:num_of_channels]]

if include_latest:
latest_channel = f"latest/edge/{flavor}"
final_channels.append(latest_channel)

return final_channels
57 changes: 25 additions & 32 deletions tests/integration/tests/test_version_upgrades.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,44 @@
LOG = logging.getLogger(__name__)


@pytest.mark.node_count(3)
@pytest.mark.node_count(1)
@pytest.mark.no_setup()
@pytest.mark.xfail("cilium failures are blocking this from working")
#@pytest.mark.xfail("cilium failures are blocking this from working")
@pytest.mark.skipif(
not config.VERSION_UPGRADE_CHANNELS, reason="No upgrade channels configured"
)
def test_version_upgrades(instances: List[harness.Instance]):
channels = config.VERSION_UPGRADE_CHANNELS
cp = instances[0]
joining_cp = instances[1]
worker = instances[2]

if channels[0].lower() == "recent":
if len(channel) != 3:
pytest.fail("'recent' requires the number of releases as second argument and the flavour as third argument")
num_channels = int(channels[1])
flavour = channels[2]
channels = util.get_latest_channels(num_channels, flavour)


LOG.info(f"Bootstrap node on {channels[0]} and upgrade through channels: {channels[1:]}")

# Setup the k8s snap from the bootstrap channel and setup basic configuration.
cp.exec(["snap", "install", "k8s", "--channel", channels[0]])
cp.exec(["k8s", "bootstrap"])

# Create an initial cluster
joining_cp.exec(["snap", "install", "k8s", "--channel", channels[0]])
joining_cp_token = util.get_join_token(cp, joining_cp)
joining_cp.exec(["k8s", "join-cluster", joining_cp_token])

worker.exec(["snap", "install", "k8s", "--channel", channels[0]])
worker_token = util.get_join_token(cp, worker, "--worker")
worker.exec(["k8s", "join-cluster", worker_token])

util.stubbornly(retries=30, delay_s=20).until(util.ready_nodes(cp) == 3)
util.stubbornly(retries=30, delay_s=20).until(util.ready_nodes(cp) == 1)

current_channel = channels[0]
for channel in channels[1:]:
for instance in instances:
LOG.info(
f"Upgrading {instance.id} from {current_channel} to channel {channel}"
)
# Log the current snap version on the node.
instance.exec(["snap", "info", "k8s"])

# note: the `--classic` flag will be ignored by snapd for strict snaps.
instance.exec(
["snap", "refresh", "k8s", "--channel", channel, "--classic", "--amend"]
)

# After the refresh, do not wait until all nodes are up.
# Microcluster expects other nodes to upgrade before continuing,
# hence the node does not come up until all nodes are upgraded.

# After each full upgrade, verify that all nodes are up (again)
LOG.info(
f"Upgrading {cp.id} from {current_channel} to channel {channel}"
)
# Log the current snap version on the node.
cp.exec(["snap", "info", "k8s"])

# note: the `--classic` flag will be ignored by snapd for strict snaps.
cp.exec(
["snap", "refresh", "k8s", "--channel", channel, "--classic", "--amend"]
)

util.stubbornly(retries=30, delay_s=20).until(util.ready_nodes(cp) == 3)
LOG.info(f"Upgraded {instance.id} to channel {channel}")
LOG.info(f"Upgraded {cp.id} to channel {channel}")

0 comments on commit 316f7cc

Please sign in to comment.