forked from envoyproxy/envoy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
api: Adding API major.minor.patch version (envoyproxy#15186)
This PR adds the API_VERSION to Envoy, and the api version handling class to fetch the latest and oldest supported API versions by Envoy. This is the first step in implementing the proposal in: envoyproxy#8416. Risk Level: Low (no usage in the code). Testing: Unit tests. Docs Changes: None. Release Notes: None. Platform Specific Features: None. Signed-off-by: Adi Suissa-Peleg <[email protected]>
- Loading branch information
Showing
10 changed files
with
281 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.0.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ licenses(["notice"]) # Apache 2 | |
|
||
exports_files([ | ||
"VERSION", | ||
"API_VERSION", | ||
".clang-format", | ||
]) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#pragma once | ||
|
||
#include "common/version/api_version_struct.h" | ||
|
||
// Defines the ApiVersion current version (Envoy::api_version), and oldest | ||
// version (Envoy::oldest_api_version). | ||
#include "common/version/api_version_number.h" | ||
|
||
namespace Envoy { | ||
|
||
/** | ||
* Wraps compiled in api versioning. | ||
*/ | ||
class ApiVersionInfo { | ||
public: | ||
// Returns the most recent API version that is supported by the client. | ||
static constexpr ApiVersion apiVersion() { return api_version; } | ||
|
||
// Returns the oldest API version that is supported by the client. | ||
static constexpr ApiVersion oldestApiVersion() { return oldest_api_version; } | ||
}; | ||
|
||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#pragma once | ||
#include <cstdint> | ||
|
||
namespace Envoy { | ||
|
||
/** | ||
* Api Version is defined by a <major>.<minor>.<patch> versions. | ||
*/ | ||
struct ApiVersion { | ||
uint32_t major; | ||
uint32_t minor; | ||
uint32_t patch; | ||
}; | ||
|
||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#include "common/version/api_version.h" | ||
|
||
#include "gmock/gmock.h" | ||
#include "gtest/gtest.h" | ||
|
||
namespace Envoy { | ||
|
||
// Verify assumptions about oldest version vs latest version. | ||
TEST(ApiVersionTest, OldestLatestVersionsAssumptions) { | ||
constexpr auto latest_version = ApiVersionInfo::apiVersion(); | ||
constexpr auto oldest_version = ApiVersionInfo::oldestApiVersion(); | ||
// Same major number, minor number difference is at most 1, and the oldest patch is 0. | ||
EXPECT_EQ(latest_version.major, oldest_version.major); | ||
EXPECT_TRUE(latest_version.minor >= oldest_version.minor && | ||
latest_version.minor - oldest_version.minor <= 1); | ||
EXPECT_EQ(0, oldest_version.patch); | ||
} | ||
|
||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
load("@rules_python//python:defs.bzl", "py_binary", "py_test") | ||
load( | ||
"//bazel:envoy_build_system.bzl", | ||
"envoy_package", | ||
) | ||
|
||
licenses(["notice"]) # Apache 2 | ||
|
||
envoy_package() | ||
|
||
py_binary( | ||
name = "generate_api_version_header_bin", | ||
srcs = ["generate_api_version_header.py"], | ||
main = "generate_api_version_header.py", | ||
python_version = "PY3", | ||
srcs_version = "PY3", | ||
visibility = ["//visibility:public"], | ||
) | ||
|
||
py_test( | ||
name = "generate_api_version_header_test", | ||
srcs = ["generate_api_version_header_test.py"], | ||
python_version = "PY3", | ||
srcs_version = "PY3", | ||
visibility = ["//visibility:public"], | ||
deps = [ | ||
":generate_api_version_header_bin", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#!/usr/bin/python | ||
"""Parses a file containing the API version (X.Y.Z format), and outputs (to | ||
stdout) a C++ header file with the ApiVersion value. | ||
""" | ||
from collections import namedtuple | ||
import pathlib | ||
import string | ||
import sys | ||
|
||
ApiVersion = namedtuple('ApiVersion', ['major', 'minor', 'patch']) | ||
|
||
FILE_TEMPLATE = string.Template("""#pragma once | ||
#include "common/version/api_version_struct.h" | ||
namespace Envoy { | ||
constexpr ApiVersion api_version = {$major, $minor, $patch}; | ||
constexpr ApiVersion oldest_api_version = {$oldest_major, $oldest_minor, $oldest_patch}; | ||
} // namespace Envoy""") | ||
|
||
|
||
def GenerateHeaderFile(input_path): | ||
"""Generates a c++ header file containing the api_version variable with the | ||
correct value. | ||
Args: | ||
input_path: the file containing the API version (API_VERSION). | ||
Returns: | ||
the header file contents. | ||
""" | ||
lines = pathlib.Path(input_path).read_text().splitlines() | ||
assert (len(lines) == 1) | ||
|
||
# Mapping each field to int verifies it is a valid version | ||
version = ApiVersion(*map(int, lines[0].split('.'))) | ||
oldest_version = ComputeOldestApiVersion(version) | ||
|
||
header_file_contents = FILE_TEMPLATE.substitute({ | ||
'major': version.major, | ||
'minor': version.minor, | ||
'patch': version.patch, | ||
'oldest_major': oldest_version.major, | ||
'oldest_minor': oldest_version.minor, | ||
'oldest_patch': oldest_version.patch | ||
}) | ||
return header_file_contents | ||
|
||
|
||
def ComputeOldestApiVersion(current_version: ApiVersion): | ||
"""Computest the oldest API version the client supports. According to the | ||
specification (see: api/API_VERSIONING.md), Envoy supports up to 2 most | ||
recent minor versions. Therefore if the latest API version "X.Y.Z", Envoy's | ||
oldest API version is "X.Y-1.0". Note that the major number is always the | ||
same as the latest version, and the patch number is always 0. In addition, | ||
the minor number is at least 0, and the oldest api version cannot be set | ||
to a previous major number. | ||
Args: | ||
current_version: the current API version. | ||
Returns: | ||
the oldest supported API version. | ||
""" | ||
return ApiVersion(current_version.major, max(current_version.minor - 1, 0), 0) | ||
|
||
|
||
if __name__ == '__main__': | ||
input_path = sys.argv[1] | ||
output = GenerateHeaderFile(input_path) | ||
# Print output to stdout | ||
print(output) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
"""Tests the api version header file generation. | ||
""" | ||
import generate_api_version_header | ||
from generate_api_version_header import ApiVersion | ||
import os | ||
import pathlib | ||
import string | ||
import tempfile | ||
import unittest | ||
|
||
|
||
class GenerateApiVersionHeaderTest(unittest.TestCase): | ||
EXPECTED_TEMPLATE = string.Template("""#pragma once | ||
#include "common/version/api_version_struct.h" | ||
namespace Envoy { | ||
constexpr ApiVersion api_version = {$major, $minor, $patch}; | ||
constexpr ApiVersion oldest_api_version = {$oldest_major, $oldest_minor, $oldest_patch}; | ||
} // namespace Envoy""") | ||
|
||
def setUp(self): | ||
# Using mkstemp instead of NamedTemporaryFile because in windows NT or later | ||
# the created NamedTemporaryFile cannot be reopened again (see comment in: | ||
# https://docs.python.org/3.9/library/tempfile.html#tempfile.NamedTemporaryFile) | ||
self._temp_fd, self._temp_fname = tempfile.mkstemp(text=True) | ||
|
||
def tearDown(self): | ||
# Close and delete the temp file. | ||
os.close(self._temp_fd) | ||
pathlib.Path(self._temp_fname).unlink() | ||
|
||
# General success pattern when valid file contents is detected. | ||
def SuccessfulTestTemplate(self, output_string, current_version: ApiVersion, | ||
oldest_version: ApiVersion): | ||
pathlib.Path(self._temp_fname).write_text(output_string) | ||
|
||
# Read the string from the file, and parse the version. | ||
output = generate_api_version_header.GenerateHeaderFile(self._temp_fname) | ||
expected_output = GenerateApiVersionHeaderTest.EXPECTED_TEMPLATE.substitute({ | ||
'major': current_version.major, | ||
'minor': current_version.minor, | ||
'patch': current_version.patch, | ||
'oldest_major': oldest_version.major, | ||
'oldest_minor': oldest_version.minor, | ||
'oldest_patch': oldest_version.patch | ||
}) | ||
self.assertEqual(expected_output, output) | ||
|
||
# General failure pattern when invalid file contents is detected. | ||
def FailedTestTemplate(self, output_string, assertion_error_type): | ||
pathlib.Path(self._temp_fname).write_text(output_string) | ||
|
||
# Read the string from the file, and expect version parsing to fail. | ||
with self.assertRaises(assertion_error_type, | ||
msg='The call to GenerateHeaderFile should have thrown an exception'): | ||
generate_api_version_header.GenerateHeaderFile(self._temp_fname) | ||
|
||
def test_valid_version(self): | ||
self.SuccessfulTestTemplate('1.2.3', ApiVersion(1, 2, 3), ApiVersion(1, 1, 0)) | ||
|
||
def test_valid_version_newline(self): | ||
self.SuccessfulTestTemplate('3.2.1\n', ApiVersion(3, 2, 1), ApiVersion(3, 1, 0)) | ||
|
||
def test_invalid_version_string(self): | ||
self.FailedTestTemplate('1.2.abc3', ValueError) | ||
|
||
def test_invalid_version_partial(self): | ||
self.FailedTestTemplate('1.2.', ValueError) | ||
|
||
def test_empty_file(self): | ||
# Not writing anything to the file | ||
self.FailedTestTemplate('', AssertionError) | ||
|
||
def test_invalid_multiple_lines(self): | ||
self.FailedTestTemplate('1.2.3\n1.2.3', AssertionError) | ||
|
||
def test_valid_oldest_api_version(self): | ||
expected_latest_oldest_pairs = [(ApiVersion(3, 2, 2), ApiVersion(3, 1, 0)), | ||
(ApiVersion(4, 5, 30), ApiVersion(4, 4, 0)), | ||
(ApiVersion(1, 1, 5), ApiVersion(1, 0, 0)), | ||
(ApiVersion(2, 0, 3), ApiVersion(2, 0, 0))] | ||
|
||
for latest_version, expected_oldest_version in expected_latest_oldest_pairs: | ||
self.assertEqual(expected_oldest_version, | ||
generate_api_version_header.ComputeOldestApiVersion(latest_version)) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |