| import logging |
| |
| from collections import namedtuple |
| |
| from dtest import RUN_STATIC_UPGRADE_MATRIX |
| from conftest import cassandra_dir_and_version |
| |
| import ccmlib.repository |
| from ccmlib.common import get_version_from_build |
| |
| from enum import Enum |
| |
| logger = logging.getLogger(__name__) |
| |
| # UpgradePath's contain data about upgrade paths we wish to test |
| # They also contain VersionMeta's for each version the path is testing |
| UpgradePath = namedtuple('UpgradePath', ('name', 'starting_version', 'upgrade_version', 'starting_meta', 'upgrade_meta')) |
| |
| VERSION_FAMILY = None |
| CONFIG = None |
| |
| |
| def is_same_family_current_to_indev(origin, destination): |
| """ |
| Within a version family it is useful to test that a prior release can upgrade to the indev version |
| """ |
| return origin.family == destination.family and origin.variant == "current" and destination.variant == "indev" |
| |
| |
| class VersionSelectionStrategies(Enum): |
| """ |
| Test upgrading from indev -> indev, current -> current across versions, and current -> indev within a version |
| """ |
| BOTH=(lambda origin, destination: (origin.variant == destination.variant) or is_same_family_current_to_indev(origin,destination)) |
| """ |
| Exclusively test in development branches so your bug fixes show up |
| """ |
| INDEV=(lambda origin, destination: origin.variant == 'indev' and destination.variant == 'indev' or is_same_family_current_to_indev(origin, destination),) |
| """ |
| Test upgrading from releases to the latest release as well as from the current release to the indev tip |
| within the same version. |
| """ |
| RELEASES=(lambda origin, destination: not VersionSelectionStrategies.INDEV.value[0](origin, destination) or is_same_family_current_to_indev(origin, destination),) |
| |
| |
| def set_config(config): |
| global CONFIG |
| CONFIG = config |
| set_version_family() |
| |
| |
| def set_version_family(): |
| """ |
| Detects the version family (line) using dtest.py:CASSANDRA_VERSION_FROM_BUILD |
| """ |
| # todo CASSANDRA-14421 |
| # current_version = CASSANDRA_VERSION_FROM_BUILD |
| # There are times when we want to know the C* version we're testing against |
| # before we call Tester.setUp. In the general case, we can't know that -- the |
| # test method could use any version it wants for self.cluster. However, we can |
| # get the version from build.xml in the C* repository specified by |
| # CASSANDRA_VERSION or CASSANDRA_DIR. This should use the same resolution |
| # strategy as the actual checkout code in Tester.setUp; if it does not, that is |
| # a bug. |
| cassandra_version_slug = CONFIG.getoption("--cassandra-version") |
| cassandra_dir = CONFIG.getoption("--cassandra-dir") or CONFIG.getini("cassandra_dir") |
| # Prefer CASSANDRA_VERSION if it's set in the environment. If not, use CASSANDRA_DIR |
| if cassandra_version_slug: |
| # fetch but don't build the specified C* version |
| ccm_repo_cache_dir, _ = ccmlib.repository.setup(cassandra_version_slug) |
| current_version = get_version_from_build(ccm_repo_cache_dir) |
| else: |
| current_version = get_version_from_build(cassandra_dir) |
| |
| if current_version.vstring.startswith('2.0'): |
| version_family = '2.0.x' |
| elif current_version.vstring.startswith('2.1'): |
| version_family = '2.1.x' |
| elif current_version.vstring.startswith('2.2'): |
| version_family = '2.2.x' |
| elif current_version.vstring.startswith('3.0'): |
| version_family = '3.0.x' |
| elif '3.1' <= current_version < '4.0': |
| version_family = '3.x' |
| elif '4.0' <= current_version < '4.1': |
| version_family = 'trunk' |
| else: |
| # when this occurs, it's time to update this manifest a bit! |
| raise RuntimeError("4.1+ not yet supported on upgrade tests!") |
| |
| global VERSION_FAMILY |
| VERSION_FAMILY = version_family |
| logger.info("Setting version family to %s\n" % VERSION_FAMILY) |
| |
| |
| class VersionMeta(namedtuple('_VersionMeta', ('name', 'family', 'variant', 'version', 'min_proto_v', 'max_proto_v', 'java_versions'))): |
| """ |
| VersionMeta's are namedtuples that capture data about version family, protocols supported, and current version identifiers |
| they must have a 'variant' value of 'current' or 'indev', where 'current' means most recent released version, |
| 'indev' means where changing code is found. |
| """ |
| @property |
| def java_version(self): |
| return max(self.java_versions) |
| |
| @property |
| def matches_current_env_version_family(self): |
| """ |
| Returns boolean indicating whether this meta matches the current version family of the environment. |
| |
| e.g. Returns true if the current env version family is 3.x and the meta's family attribute is a match. |
| """ |
| return self.family == VERSION_FAMILY |
| |
| def clone_with_local_env_version(self): |
| """ |
| Returns a new object cloned from this one, with the version replaced with the local env version. |
| """ |
| cassandra_dir, cassandra_version = cassandra_dir_and_version(CONFIG) |
| if cassandra_version: |
| return self._replace(version=cassandra_version) |
| return self._replace(version="clone:{}".format(cassandra_dir)) |
| |
| |
| indev_2_1_x = VersionMeta(name='indev_2_1_x', family='2.1', variant='indev', version='github:apache/cassandra-2.1', min_proto_v=1, max_proto_v=3, java_versions=(7, 8)) |
| current_2_1_x = VersionMeta(name='current_2_1_x', family='2.1', variant='current', version='2.1.20', min_proto_v=1, max_proto_v=3, java_versions=(7, 8)) |
| |
| indev_2_2_x = VersionMeta(name='indev_2_2_x', family='2.2', variant='indev', version='github:apache/cassandra-2.2', min_proto_v=1, max_proto_v=4, java_versions=(7, 8)) |
| current_2_2_x = VersionMeta(name='current_2_2_x', family='2.2', variant='current', version='2.2.13', min_proto_v=1, max_proto_v=4, java_versions=(7, 8)) |
| |
| indev_3_0_x = VersionMeta(name='indev_3_0_x', family='3.0', variant='indev', version='github:apache/cassandra-3.0', min_proto_v=3, max_proto_v=4, java_versions=(8,)) |
| current_3_0_x = VersionMeta(name='current_3_0_x', family='3.0', variant='current', version='3.0.17', min_proto_v=3, max_proto_v=4, java_versions=(8,)) |
| |
| indev_3_11_x = VersionMeta(name='indev_3_11_x', family='3.11', variant='indev', version='github:apache/cassandra-3.11', min_proto_v=3, max_proto_v=4, java_versions=(8,)) |
| current_3_11_x = VersionMeta(name='current_3_11_x', family='3.11', variant='current', version='github:apache/cassandra-3.11', min_proto_v=3, max_proto_v=4, java_versions=(8,)) |
| |
| indev_trunk = VersionMeta(name='indev_trunk', family='trunk', variant='indev', version='github:apache/trunk', min_proto_v=4, max_proto_v=5, java_versions=(8,)) |
| |
| |
| # MANIFEST maps a VersionMeta representing a line/variant to a list of other VersionMeta's representing supported upgrades |
| # Note on versions: 2.0 must upgrade to 2.1. Once at 2.1 or newer, upgrade is supported to any later version, including trunk (for now). |
| # "supported upgrade" means a few basic things, for an upgrade of version 'A' to higher version 'B': |
| # 1) The cluster will function in a mixed-version state, with some nodes on version A and some nodes on version B. Schema modifications are not supported on mixed-version clusters. |
| # 2) Features exclusive to version B may not work until all nodes are running version B. |
| # 3) Nodes upgraded to version B can read data stored by the predecessor version A, and from a data standpoint will function the same as if they always ran version B. |
| # 4) If a new sstable format is present in version B, writes will occur in that format after upgrade. Running sstableupgrade on version B will proactively convert version A sstables to version B. |
| MANIFEST = { |
| indev_2_1_x: [indev_2_2_x, current_2_2_x, indev_3_0_x, current_3_0_x, indev_3_11_x, current_3_11_x], |
| current_2_1_x: [indev_2_1_x, indev_2_2_x, current_2_2_x, indev_3_0_x, current_3_0_x, indev_3_11_x, current_3_11_x], |
| |
| indev_2_2_x: [indev_3_0_x, current_3_0_x, indev_3_11_x, current_3_11_x], |
| current_2_2_x: [indev_2_2_x, indev_3_0_x, current_3_0_x, indev_3_11_x, current_3_11_x], |
| |
| indev_3_0_x: [indev_3_11_x, current_3_11_x], |
| current_3_0_x: [indev_3_0_x, indev_3_11_x, current_3_11_x, indev_trunk], |
| |
| current_3_11_x: [indev_3_11_x, indev_trunk], |
| indev_3_11_x: [indev_trunk] |
| } |
| |
| # Local env and custom path testing instructions. Use these steps to REPLACE the normal upgrade test cases with your own. |
| # 1) Add a VersionMeta for each version you wish to test (see examples below). Update the name, family, version, and protocol restrictions as needed. Use a unique name for each VersionMeta. |
| # 2) Update OVERRIDE_MANIFEST (see example below). |
| # 3) If you want to test using local code, set the version attribute using local slugs in the format 'local:/path/to/cassandra/:branch_name' |
| # 4) Run the tests! |
| # To run all, use 'nosetests -v upgrade_tests/'. To run specific tests, use 'nosetests -vs --collect-only' to preview the test names, then run nosetests using the desired test name. |
| # Note that nosetests outputs test names in a format that needs to be tweaked a bit before they will run from the command line. |
| custom_1 = VersionMeta(name='custom_branch_1', family='2.1.x', variant='indev', version='local:some_branch', min_proto_v=3, max_proto_v=4, java_versions=(7, 8)) |
| custom_2 = VersionMeta(name='custom_branch_2', family='2.2.x', variant='indev', version='git:trunk', min_proto_v=3, max_proto_v=4, java_versions=(7, 8)) |
| custom_3 = VersionMeta(name='custom_branch_3', family='3.0.x', variant='indev', version='git:cassandra-3.5', min_proto_v=3, max_proto_v=4, java_versions=(7, 8)) |
| custom_4 = VersionMeta(name='custom_branch_4', family='3.x', variant='indev', version='git:cassandra-3.6', min_proto_v=3, max_proto_v=4, java_versions=(7, 8)) |
| OVERRIDE_MANIFEST = { |
| # EXAMPLE: |
| # custom_1: [custom_2, custom_3], # creates a test of custom_1 -> custom_2, and another test from custom_1 -> custom_3 |
| # custom_3: [custom_4] # creates a test of custom_3 -> custom_4 |
| } |
| |
| |
| def _have_common_proto(origin_meta, destination_meta): |
| """ |
| Takes two VersionMeta objects, in order of test from start version to next version. |
| Returns a boolean indicating if the given VersionMetas have a common protocol version. |
| """ |
| return origin_meta.max_proto_v >= destination_meta.min_proto_v |
| |
| def build_upgrade_pairs(): |
| """ |
| Using the manifest (above), builds a set of valid upgrades, according to current testing practices. |
| |
| Returns a list of UpgradePath's. |
| """ |
| valid_upgrade_pairs = [] |
| manifest = OVERRIDE_MANIFEST or MANIFEST |
| |
| configured_strategy = CONFIG.getoption("--upgrade-version-selection").upper() |
| version_select_strategy = VersionSelectionStrategies[configured_strategy].value[0] |
| |
| for origin_meta, destination_metas in list(manifest.items()): |
| for destination_meta in destination_metas: |
| if not version_select_strategy(origin_meta, destination_meta): |
| continue |
| |
| if not (origin_meta and destination_meta): # None means we don't care about that version, which means we don't care about iterations involving it either |
| logger.debug("skipping class creation as a version is undefined (this is normal), versions: {} and {}".format(origin_meta, destination_meta)) |
| continue |
| |
| if not _have_common_proto(origin_meta, destination_meta): |
| logger.debug("skipping class creation, no compatible protocol version between {} and {}".format(origin_meta.name, destination_meta.name)) |
| continue |
| |
| path_name = 'Upgrade_' + origin_meta.name + '_To_' + destination_meta.name |
| |
| if not (RUN_STATIC_UPGRADE_MATRIX or OVERRIDE_MANIFEST): |
| if destination_meta.matches_current_env_version_family: |
| # looks like this test should actually run in the current env, so let's set the final version to match the env exactly |
| oldmeta = destination_meta |
| newmeta = destination_meta.clone_with_local_env_version() |
| logger.debug("{} appears applicable to current env. Overriding final test version from {} to {}".format(path_name, oldmeta.version, newmeta.version)) |
| destination_meta = newmeta |
| |
| valid_upgrade_pairs.append( |
| UpgradePath( |
| name=path_name, |
| starting_version=origin_meta.version, |
| upgrade_version=destination_meta.version, |
| starting_meta=origin_meta, |
| upgrade_meta=destination_meta |
| ) |
| ) |
| |
| return valid_upgrade_pairs |