Skip to content
Merged
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
6 changes: 0 additions & 6 deletions HACKING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ Magnum Style Commandments
Magnum Specific Commandments
----------------------------

- [M302] Change assertEqual(A is not None) by optimal assert like
assertIsNotNone(A).
- [M310] timeutils.utcnow() wrapper must be used instead of direct calls to
datetime.datetime.utcnow() to make it easy to override its return value.
- [M316] Change assertTrue(isinstance(A, B)) by optimal assert like
assertIsInstance(A, B).
- [M322] Method's default argument shouldn't be mutable.
- [M336] Must use a dict comprehension instead of a dict constructor
with a sequence of key-value pairs.
- [M338] Use assertIn/NotIn(A, B) rather than assertEqual(A in B, True/False).
- [M340] Check for explicit import of the _ function.
- [M352] LOG.warn is deprecated. Enforce use of LOG.warning.
- [M353] String interpolation should be delayed at logging calls.
23 changes: 15 additions & 8 deletions api-ref/source/urls.inc
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@
=================

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

Note that if using OpenStack Identity service API v2, ``url`` can be
represented via ``adminURL``, ``internalURL`` or ``publicURL`` in endpoint
catalog. In Identity service API v3, ``url`` is represented with field
``interface`` including ``admin``, ``internal`` and ``public``.
.. note::

If using OpenStack Identity service API v2, ``url`` can be represented via
``adminURL``, ``internalURL`` or ``publicURL`` in endpoint catalog. In
Identity service API v3, ``url`` is represented with field ``interface``
including ``admin``, ``internal`` and ``public``.

For instance, if the ``url`` is
``http://my-container-infra.org/magnum/v1`` then the full API call for
Expand Down
38 changes: 20 additions & 18 deletions doc/source/contributor/api-microversion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ Background
Magnum uses a framework we call 'API Microversions' for allowing changes
to the API while preserving backward compatibility. The basic idea is
that a user has to explicitly ask for their request to be treated with
a particular version of the API. So breaking changes can be added to
a particular version of the API. Breaking changes can be added to
the API without breaking users who don't specifically ask for it. This
is done with an HTTP header ``OpenStack-API-Version`` which has as its
value a string containing the name of the service, ``container-infra``,
and a monotonically increasing semantic version number starting
from ``1.1``.
value a string containing the name of the service,
``container-infrastructure-management`` or one of its aliases,
``container-infrastructure`` or ``container-infra``, along with a monotonically
increasing semantic version number starting from ``1.1``.
The full form of the header takes the form::

OpenStack-API-Version: container-infra 1.1
OpenStack-API-Version: container-infrastructure-management 1.1

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

- the Request

- the list of resource urls which exist on the server
- the list of resource URLs which exist on the server

Example: adding a new clusters/{ID}/foo which didn't exist in a
Example: adding a new ``clusters/{ID}/foo`` which didn't exist in a
previous version of the code

- the list of query parameters that are valid on urls
- the list of query parameters that are valid on URLs

Example: adding a new parameter ``is_yellow`` clusters/{ID}?is_yellow=True
Example: adding a new parameter ``clusters/{ID}?is_yellow=True``

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

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

- new headers accepted on a request

- the list of attributes and data structures accepted.

Example: adding a new attribute 'locked': True/False to the request body

Example: adding a new attribute ``'locked': ``<bool>`` to the request body

- the Response

- the list of attributes and data structures returned

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

- the allowed values of non free form fields

Example: adding a new allowed ``status`` to clusters/{ID}
Example: adding a new allowed ``status`` to ``clusters/{ID}``

- the list of status codes allowed for a particular request

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

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

.. note:: Fixing a bug so that a 400+ code is returned rather than a 500 or
.. note::

Fixing a bug so that a 400+ code is returned rather than a 500 or
503 does not require a microversion change. It's assumed that clients are
not expected to handle a 500 or 503 response and therefore should not
need to opt-in to microversion changes that fixes a 500 or 503 response
from happening.

According to the OpenStack API Working Group, a
**500 Internal Server Error** should **not** be returned to the user for
failures due to user error that can be fixed by changing the request on
Expand All @@ -89,7 +92,6 @@ changed. The user contract covers many kinds of information such as:
The following flow chart attempts to walk through the process of "do
we need a microversion".


.. graphviz::

digraph states {
Expand Down
10 changes: 8 additions & 2 deletions magnum/api/controllers/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ class Version(object):
max_string = 'OpenStack-API-Maximum-Version'
"""HTTP response header"""

service_string = 'container-infra'
service_strings = (
'container-infrastructure-management',
'container-infrastructure',
'container-infra',
)

service_string = service_strings[0]

def __init__(self, headers, default_version, latest_version,
from_string=None):
Expand Down Expand Up @@ -99,7 +105,7 @@ def parse_headers(headers, default_version, latest_version):
if version_str.lower() == 'latest':
version_service, version_str = latest_version.split()

if version_service != Version.service_string:
if version_service not in Version.service_strings:
raise exc.HTTPNotAcceptable(_(
"Invalid service type for %s header") % Version.string)
try:
Expand Down
42 changes: 0 additions & 42 deletions magnum/hacking/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,6 @@
UNDERSCORE_IMPORT_FILES = []

mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
assert_equal_in_end_with_true_or_false_re = re.compile(
r"assertEqual\((\w|[][.'\"])+ in (\w|[][.'\", ])+, (True|False)\)")
assert_equal_in_start_with_true_or_false_re = re.compile(
r"assertEqual\((True|False), (\w|[][.'\"])+ in (\w|[][.'\", ])+\)")
assert_equal_with_is_not_none_re = re.compile(
r"assertEqual\(.*?\s+is+\s+not+\s+None\)$")
assert_true_isinstance_re = re.compile(
r"(.)*assertTrue\(isinstance\((\w|\.|\'|\"|\[|\])+, "
r"(\w|\.|\'|\"|\[|\])+\)\)")
dict_constructor_with_list_copy_re = re.compile(r".*\bdict\((\[)?(\(|\[)")
log_translation = re.compile(
r"(.)*LOG\.(audit|error|critical)\(\s*('|\")")
Expand All @@ -67,39 +58,6 @@ def no_mutable_default_args(logical_line):
yield (0, msg)


@core.flake8ext
def assert_equal_not_none(logical_line):
"""Check for assertEqual(A is not None) sentences M302"""
msg = "M302: assertEqual(A is not None) sentences not allowed."
res = assert_equal_with_is_not_none_re.search(logical_line)
if res:
yield (0, msg)


@core.flake8ext
def assert_true_isinstance(logical_line):
"""Check for assertTrue(isinstance(a, b)) sentences

M316
"""
if assert_true_isinstance_re.match(logical_line):
yield (0, "M316: assertTrue(isinstance(a, b)) sentences not allowed")


@core.flake8ext
def assert_equal_in(logical_line):
"""Check for assertEqual(True|False, A in B), assertEqual(A in B, True|False)

M338
""" # noqa: E501
res = (assert_equal_in_start_with_true_or_false_re.search(logical_line) or
assert_equal_in_end_with_true_or_false_re.search(logical_line))
if res:
yield (0, "M338: Use assertIn/NotIn(A, B) rather than "
"assertEqual(A in B, True/False) when checking collection "
"contents.")


@core.flake8ext
def use_timeutils_utcnow(logical_line, filename):
# tools are OK to use the standard datetime module
Expand Down
52 changes: 26 additions & 26 deletions magnum/tests/unit/api/controllers/v1/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,17 +943,17 @@ def test_create_cluster_with_keypair(self):
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
self.assertEqual('keypair2', cluster[0].keypair)
cluster = self.mock_cluster_create.call_args.args[0]
self.assertEqual('keypair2', cluster.keypair)

def test_create_cluster_without_keypair(self):
bdict = apiutils.cluster_post_data()
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
cluster = self.mock_cluster_create.call_args.args[0]
# Verify keypair from ClusterTemplate is used
self.assertEqual('keypair1', cluster[0].keypair)
self.assertEqual('keypair1', cluster.keypair)

def test_create_cluster_with_multi_keypair_same_name(self):
bdict = apiutils.cluster_post_data()
Expand All @@ -969,17 +969,17 @@ def test_create_cluster_with_docker_volume_size(self):
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
self.assertEqual(3, cluster[0].docker_volume_size)
cluster = self.mock_cluster_create.call_args.args[0]
self.assertEqual(3, cluster.docker_volume_size)

def test_create_cluster_with_labels(self):
bdict = apiutils.cluster_post_data()
bdict['labels'] = {'key': 'value'}
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
self.assertEqual({'key': 'value'}, cluster[0].labels)
cluster = self.mock_cluster_create.call_args.args[0]
self.assertEqual({'key': 'value'}, cluster.labels)

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

def test_create_cluster_without_labels(self):
bdict = apiutils.cluster_post_data()
bdict.pop('labels')
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
cluster = self.mock_cluster_create.call_args.args[0]
# Verify labels from ClusterTemplate is used
self.assertEqual({'key1': u'val1', 'key2': u'val2'}, cluster[0].labels)
self.assertEqual({'key1': u'val1', 'key2': u'val2'}, cluster.labels)

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

def test_create_cluster_without_master_flavor_id(self):
bdict = apiutils.cluster_post_data()
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
cluster = self.mock_cluster_create.call_args.args[0]
# Verify master_flavor_id from ClusterTemplate is used
self.assertEqual('m1.small', cluster[0].master_flavor_id)
self.assertEqual('m1.small', cluster.master_flavor_id)

def test_create_cluster_with_flavor_id(self):
bdict = apiutils.cluster_post_data()
bdict['flavor_id'] = 'm2.small'
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
self.assertEqual('m2.small', cluster[0].flavor_id)
cluster = self.mock_cluster_create.call_args.args[0]
self.assertEqual('m2.small', cluster.flavor_id)

def test_create_cluster_without_flavor_id(self):
bdict = apiutils.cluster_post_data()
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
cluster = self.mock_cluster_create.call_args.args[0]
# Verify flavor_id from ClusterTemplate is used
self.assertEqual('m1.small', cluster[0].flavor_id)
self.assertEqual('m1.small', cluster.flavor_id)

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

def test_create_cluster_with_merge_labels(self):
self.cluster_template.labels = {'label1': 'value1', 'label2': 'value2'}
Expand All @@ -1087,10 +1087,10 @@ def test_create_cluster_with_merge_labels(self):
response = self.post_json('/clusters', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)
cluster, timeout = self.mock_cluster_create.call_args
cluster = self.mock_cluster_create.call_args.args[0]
expected = self.cluster_template.labels
expected.update(cluster_labels)
self.assertEqual(expected, cluster[0].labels)
self.assertEqual(expected, cluster.labels)

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


class TestDelete(api_base.FunctionalTest):
Expand Down
Loading
Loading