Skip to content

Commit 2bd8677

Browse files
authored
Merge pull request #254 from stackhpc/upstream/master-2026-03-09
Synchronise master with upstream
2 parents 7922fac + a9e240f commit 2bd8677

11 files changed

Lines changed: 86 additions & 178 deletions

File tree

HACKING.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,10 @@ Magnum Style Commandments
88
Magnum Specific Commandments
99
----------------------------
1010

11-
- [M302] Change assertEqual(A is not None) by optimal assert like
12-
assertIsNotNone(A).
1311
- [M310] timeutils.utcnow() wrapper must be used instead of direct calls to
1412
datetime.datetime.utcnow() to make it easy to override its return value.
15-
- [M316] Change assertTrue(isinstance(A, B)) by optimal assert like
16-
assertIsInstance(A, B).
1713
- [M322] Method's default argument shouldn't be mutable.
1814
- [M336] Must use a dict comprehension instead of a dict constructor
1915
with a sequence of key-value pairs.
20-
- [M338] Use assertIn/NotIn(A, B) rather than assertEqual(A in B, True/False).
2116
- [M340] Check for explicit import of the _ function.
2217
- [M352] LOG.warn is deprecated. Enforce use of LOG.warning.
23-
- [M353] String interpolation should be delayed at logging calls.

api-ref/source/urls.inc

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,22 @@
55
=================
66

77
All API calls through the rest of this document require authentication
8-
with the OpenStack Identity service. They also required a ``url`` that
9-
is extracted from the Identity token of type
10-
``container-infra``. This will be the root url that every call below will be
11-
added to build a full path.
8+
with the OpenStack Identity service. The Identity service includes a so-called
9+
Service Catalog in responses to valid requests for a new token. This service
10+
catalog defines a list of services, identified by both a name and a service
11+
type, along with a list of endpoints. In deployments where Magnum is installed,
12+
Magnum should appear in this list using the service type,
13+
``container-infrastructure-management``, or one of the aliases,
14+
``container-infrastructure`` or ``container-infra``. The endpoint URL provided
15+
will form the root url that every call below will be added to build a full
16+
path.
1217

13-
Note that if using OpenStack Identity service API v2, ``url`` can be
14-
represented via ``adminURL``, ``internalURL`` or ``publicURL`` in endpoint
15-
catalog. In Identity service API v3, ``url`` is represented with field
16-
``interface`` including ``admin``, ``internal`` and ``public``.
18+
.. note::
19+
20+
If using OpenStack Identity service API v2, ``url`` can be represented via
21+
``adminURL``, ``internalURL`` or ``publicURL`` in endpoint catalog. In
22+
Identity service API v3, ``url`` is represented with field ``interface``
23+
including ``admin``, ``internal`` and ``public``.
1724

1825
For instance, if the ``url`` is
1926
``http://my-container-infra.org/magnum/v1`` then the full API call for

doc/source/contributor/api-microversion.rst

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ Background
77
Magnum uses a framework we call 'API Microversions' for allowing changes
88
to the API while preserving backward compatibility. The basic idea is
99
that a user has to explicitly ask for their request to be treated with
10-
a particular version of the API. So breaking changes can be added to
10+
a particular version of the API. Breaking changes can be added to
1111
the API without breaking users who don't specifically ask for it. This
1212
is done with an HTTP header ``OpenStack-API-Version`` which has as its
13-
value a string containing the name of the service, ``container-infra``,
14-
and a monotonically increasing semantic version number starting
15-
from ``1.1``.
13+
value a string containing the name of the service,
14+
``container-infrastructure-management`` or one of its aliases,
15+
``container-infrastructure`` or ``container-infra``, along with a monotonically
16+
increasing semantic version number starting from ``1.1``.
1617
The full form of the header takes the form::
1718

18-
OpenStack-API-Version: container-infra 1.1
19+
OpenStack-API-Version: container-infrastructure-management 1.1
1920

2021
If a user makes a request without specifying a version, they will get
2122
the ``BASE_VER`` as defined in
@@ -31,37 +32,36 @@ changed. The user contract covers many kinds of information such as:
3132

3233
- the Request
3334

34-
- the list of resource urls which exist on the server
35+
- the list of resource URLs which exist on the server
3536

36-
Example: adding a new clusters/{ID}/foo which didn't exist in a
37+
Example: adding a new ``clusters/{ID}/foo`` which didn't exist in a
3738
previous version of the code
3839

39-
- the list of query parameters that are valid on urls
40+
- the list of query parameters that are valid on URLs
4041

41-
Example: adding a new parameter ``is_yellow`` clusters/{ID}?is_yellow=True
42+
Example: adding a new parameter ``clusters/{ID}?is_yellow=True``
4243

4344
- the list of query parameter values for non free form fields
4445

45-
Example: parameter filter_by takes a small set of constants/enums "A",
46-
"B", "C". Adding support for new enum "D".
46+
Example: parameter ``filter_by`` takes a small set of constants/enums
47+
``A``, ``B``, ``C``. Adding support for new enum ``D``.
4748

4849
- new headers accepted on a request
4950

5051
- the list of attributes and data structures accepted.
5152

52-
Example: adding a new attribute 'locked': True/False to the request body
53-
53+
Example: adding a new attribute ``'locked': ``<bool>`` to the request body
5454

5555
- the Response
5656

5757
- the list of attributes and data structures returned
5858

59-
Example: adding a new attribute 'locked': True/False to the output
60-
of clusters/{ID}
59+
Example: adding a new attribute ``'locked': <bool>`` to the output
60+
of ``clusters/{ID}``
6161

6262
- the allowed values of non free form fields
6363

64-
Example: adding a new allowed ``status`` to clusters/{ID}
64+
Example: adding a new allowed ``status`` to ``clusters/{ID}``
6565

6666
- the list of status codes allowed for a particular request
6767

@@ -74,11 +74,14 @@ changed. The user contract covers many kinds of information such as:
7474

7575
Example: changing the return code of an API from 501 to 400.
7676

77-
.. note:: Fixing a bug so that a 400+ code is returned rather than a 500 or
77+
.. note::
78+
79+
Fixing a bug so that a 400+ code is returned rather than a 500 or
7880
503 does not require a microversion change. It's assumed that clients are
7981
not expected to handle a 500 or 503 response and therefore should not
8082
need to opt-in to microversion changes that fixes a 500 or 503 response
8183
from happening.
84+
8285
According to the OpenStack API Working Group, a
8386
**500 Internal Server Error** should **not** be returned to the user for
8487
failures due to user error that can be fixed by changing the request on
@@ -89,7 +92,6 @@ changed. The user contract covers many kinds of information such as:
8992
The following flow chart attempts to walk through the process of "do
9093
we need a microversion".
9194

92-
9395
.. graphviz::
9496

9597
digraph states {

magnum/api/controllers/versions.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ class Version(object):
5353
max_string = 'OpenStack-API-Maximum-Version'
5454
"""HTTP response header"""
5555

56-
service_string = 'container-infra'
56+
service_strings = (
57+
'container-infrastructure-management',
58+
'container-infrastructure',
59+
'container-infra',
60+
)
61+
62+
service_string = service_strings[0]
5763

5864
def __init__(self, headers, default_version, latest_version,
5965
from_string=None):
@@ -99,7 +105,7 @@ def parse_headers(headers, default_version, latest_version):
99105
if version_str.lower() == 'latest':
100106
version_service, version_str = latest_version.split()
101107

102-
if version_service != Version.service_string:
108+
if version_service not in Version.service_strings:
103109
raise exc.HTTPNotAcceptable(_(
104110
"Invalid service type for %s header") % Version.string)
105111
try:

magnum/hacking/checks.py

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,6 @@
3434
UNDERSCORE_IMPORT_FILES = []
3535

3636
mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
37-
assert_equal_in_end_with_true_or_false_re = re.compile(
38-
r"assertEqual\((\w|[][.'\"])+ in (\w|[][.'\", ])+, (True|False)\)")
39-
assert_equal_in_start_with_true_or_false_re = re.compile(
40-
r"assertEqual\((True|False), (\w|[][.'\"])+ in (\w|[][.'\", ])+\)")
41-
assert_equal_with_is_not_none_re = re.compile(
42-
r"assertEqual\(.*?\s+is+\s+not+\s+None\)$")
43-
assert_true_isinstance_re = re.compile(
44-
r"(.)*assertTrue\(isinstance\((\w|\.|\'|\"|\[|\])+, "
45-
r"(\w|\.|\'|\"|\[|\])+\)\)")
4637
dict_constructor_with_list_copy_re = re.compile(r".*\bdict\((\[)?(\(|\[)")
4738
log_translation = re.compile(
4839
r"(.)*LOG\.(audit|error|critical)\(\s*('|\")")
@@ -67,39 +58,6 @@ def no_mutable_default_args(logical_line):
6758
yield (0, msg)
6859

6960

70-
@core.flake8ext
71-
def assert_equal_not_none(logical_line):
72-
"""Check for assertEqual(A is not None) sentences M302"""
73-
msg = "M302: assertEqual(A is not None) sentences not allowed."
74-
res = assert_equal_with_is_not_none_re.search(logical_line)
75-
if res:
76-
yield (0, msg)
77-
78-
79-
@core.flake8ext
80-
def assert_true_isinstance(logical_line):
81-
"""Check for assertTrue(isinstance(a, b)) sentences
82-
83-
M316
84-
"""
85-
if assert_true_isinstance_re.match(logical_line):
86-
yield (0, "M316: assertTrue(isinstance(a, b)) sentences not allowed")
87-
88-
89-
@core.flake8ext
90-
def assert_equal_in(logical_line):
91-
"""Check for assertEqual(True|False, A in B), assertEqual(A in B, True|False)
92-
93-
M338
94-
""" # noqa: E501
95-
res = (assert_equal_in_start_with_true_or_false_re.search(logical_line) or
96-
assert_equal_in_end_with_true_or_false_re.search(logical_line))
97-
if res:
98-
yield (0, "M338: Use assertIn/NotIn(A, B) rather than "
99-
"assertEqual(A in B, True/False) when checking collection "
100-
"contents.")
101-
102-
10361
@core.flake8ext
10462
def use_timeutils_utcnow(logical_line, filename):
10563
# tools are OK to use the standard datetime module

magnum/tests/unit/api/controllers/v1/test_cluster.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -943,17 +943,17 @@ def test_create_cluster_with_keypair(self):
943943
response = self.post_json('/clusters', bdict)
944944
self.assertEqual('application/json', response.content_type)
945945
self.assertEqual(202, response.status_int)
946-
cluster, timeout = self.mock_cluster_create.call_args
947-
self.assertEqual('keypair2', cluster[0].keypair)
946+
cluster = self.mock_cluster_create.call_args.args[0]
947+
self.assertEqual('keypair2', cluster.keypair)
948948

949949
def test_create_cluster_without_keypair(self):
950950
bdict = apiutils.cluster_post_data()
951951
response = self.post_json('/clusters', bdict)
952952
self.assertEqual('application/json', response.content_type)
953953
self.assertEqual(202, response.status_int)
954-
cluster, timeout = self.mock_cluster_create.call_args
954+
cluster = self.mock_cluster_create.call_args.args[0]
955955
# Verify keypair from ClusterTemplate is used
956-
self.assertEqual('keypair1', cluster[0].keypair)
956+
self.assertEqual('keypair1', cluster.keypair)
957957

958958
def test_create_cluster_with_multi_keypair_same_name(self):
959959
bdict = apiutils.cluster_post_data()
@@ -969,17 +969,17 @@ def test_create_cluster_with_docker_volume_size(self):
969969
response = self.post_json('/clusters', bdict)
970970
self.assertEqual('application/json', response.content_type)
971971
self.assertEqual(202, response.status_int)
972-
cluster, timeout = self.mock_cluster_create.call_args
973-
self.assertEqual(3, cluster[0].docker_volume_size)
972+
cluster = self.mock_cluster_create.call_args.args[0]
973+
self.assertEqual(3, cluster.docker_volume_size)
974974

975975
def test_create_cluster_with_labels(self):
976976
bdict = apiutils.cluster_post_data()
977977
bdict['labels'] = {'key': 'value'}
978978
response = self.post_json('/clusters', bdict)
979979
self.assertEqual('application/json', response.content_type)
980980
self.assertEqual(202, response.status_int)
981-
cluster, timeout = self.mock_cluster_create.call_args
982-
self.assertEqual({'key': 'value'}, cluster[0].labels)
981+
cluster = self.mock_cluster_create.call_args.args[0]
982+
self.assertEqual({'key': 'value'}, cluster.labels)
983983

984984
def test_create_cluster_without_docker_volume_size(self):
985985
bdict = apiutils.cluster_post_data()
@@ -988,19 +988,19 @@ def test_create_cluster_without_docker_volume_size(self):
988988
response = self.post_json('/clusters', bdict)
989989
self.assertEqual('application/json', response.content_type)
990990
self.assertEqual(202, response.status_int)
991-
cluster, timeout = self.mock_cluster_create.call_args
991+
cluster = self.mock_cluster_create.call_args.args[0]
992992
# Verify docker_volume_size from ClusterTemplate is used
993-
self.assertEqual(20, cluster[0].docker_volume_size)
993+
self.assertEqual(20, cluster.docker_volume_size)
994994

995995
def test_create_cluster_without_labels(self):
996996
bdict = apiutils.cluster_post_data()
997997
bdict.pop('labels')
998998
response = self.post_json('/clusters', bdict)
999999
self.assertEqual('application/json', response.content_type)
10001000
self.assertEqual(202, response.status_int)
1001-
cluster, timeout = self.mock_cluster_create.call_args
1001+
cluster = self.mock_cluster_create.call_args.args[0]
10021002
# Verify labels from ClusterTemplate is used
1003-
self.assertEqual({'key1': u'val1', 'key2': u'val2'}, cluster[0].labels)
1003+
self.assertEqual({'key1': u'val1', 'key2': u'val2'}, cluster.labels)
10041004

10051005
def test_create_cluster_with_invalid_docker_volume_size(self):
10061006
invalid_values = [(-1, None), ('notanint', None),
@@ -1026,35 +1026,35 @@ def test_create_cluster_with_master_flavor_id(self):
10261026
response = self.post_json('/clusters', bdict)
10271027
self.assertEqual('application/json', response.content_type)
10281028
self.assertEqual(202, response.status_int)
1029-
cluster, timeout = self.mock_cluster_create.call_args
1030-
self.assertEqual('m2.small', cluster[0].master_flavor_id)
1029+
cluster = self.mock_cluster_create.call_args.args[0]
1030+
self.assertEqual('m2.small', cluster.master_flavor_id)
10311031

10321032
def test_create_cluster_without_master_flavor_id(self):
10331033
bdict = apiutils.cluster_post_data()
10341034
response = self.post_json('/clusters', bdict)
10351035
self.assertEqual('application/json', response.content_type)
10361036
self.assertEqual(202, response.status_int)
1037-
cluster, timeout = self.mock_cluster_create.call_args
1037+
cluster = self.mock_cluster_create.call_args.args[0]
10381038
# Verify master_flavor_id from ClusterTemplate is used
1039-
self.assertEqual('m1.small', cluster[0].master_flavor_id)
1039+
self.assertEqual('m1.small', cluster.master_flavor_id)
10401040

10411041
def test_create_cluster_with_flavor_id(self):
10421042
bdict = apiutils.cluster_post_data()
10431043
bdict['flavor_id'] = 'm2.small'
10441044
response = self.post_json('/clusters', bdict)
10451045
self.assertEqual('application/json', response.content_type)
10461046
self.assertEqual(202, response.status_int)
1047-
cluster, timeout = self.mock_cluster_create.call_args
1048-
self.assertEqual('m2.small', cluster[0].flavor_id)
1047+
cluster = self.mock_cluster_create.call_args.args[0]
1048+
self.assertEqual('m2.small', cluster.flavor_id)
10491049

10501050
def test_create_cluster_without_flavor_id(self):
10511051
bdict = apiutils.cluster_post_data()
10521052
response = self.post_json('/clusters', bdict)
10531053
self.assertEqual('application/json', response.content_type)
10541054
self.assertEqual(202, response.status_int)
1055-
cluster, timeout = self.mock_cluster_create.call_args
1055+
cluster = self.mock_cluster_create.call_args.args[0]
10561056
# Verify flavor_id from ClusterTemplate is used
1057-
self.assertEqual('m1.small', cluster[0].flavor_id)
1057+
self.assertEqual('m1.small', cluster.flavor_id)
10581058

10591059
def test_create_cluster_with_cinder_csi_disabled(self):
10601060
self.cluster_template.volume_driver = 'cinder'
@@ -1075,8 +1075,8 @@ def test_create_cluster_without_merge_labels(self):
10751075
response = self.post_json('/clusters', bdict)
10761076
self.assertEqual('application/json', response.content_type)
10771077
self.assertEqual(202, response.status_int)
1078-
cluster, timeout = self.mock_cluster_create.call_args
1079-
self.assertEqual(cluster_labels, cluster[0].labels)
1078+
cluster = self.mock_cluster_create.call_args.args[0]
1079+
self.assertEqual(cluster_labels, cluster.labels)
10801080

10811081
def test_create_cluster_with_merge_labels(self):
10821082
self.cluster_template.labels = {'label1': 'value1', 'label2': 'value2'}
@@ -1087,10 +1087,10 @@ def test_create_cluster_with_merge_labels(self):
10871087
response = self.post_json('/clusters', bdict)
10881088
self.assertEqual('application/json', response.content_type)
10891089
self.assertEqual(202, response.status_int)
1090-
cluster, timeout = self.mock_cluster_create.call_args
1090+
cluster = self.mock_cluster_create.call_args.args[0]
10911091
expected = self.cluster_template.labels
10921092
expected.update(cluster_labels)
1093-
self.assertEqual(expected, cluster[0].labels)
1093+
self.assertEqual(expected, cluster.labels)
10941094

10951095
def test_create_cluster_with_merge_labels_no_labels(self):
10961096
self.cluster_template.labels = {'label1': 'value1', 'label2': 'value2'}
@@ -1100,8 +1100,8 @@ def test_create_cluster_with_merge_labels_no_labels(self):
11001100
response = self.post_json('/clusters', bdict)
11011101
self.assertEqual('application/json', response.content_type)
11021102
self.assertEqual(202, response.status_int)
1103-
cluster, timeout = self.mock_cluster_create.call_args
1104-
self.assertEqual(self.cluster_template.labels, cluster[0].labels)
1103+
cluster = self.mock_cluster_create.call_args.args[0]
1104+
self.assertEqual(self.cluster_template.labels, cluster.labels)
11051105

11061106

11071107
class TestDelete(api_base.FunctionalTest):

0 commit comments

Comments
 (0)