Merge "API - Implement /events endpoint"
This commit is contained in:
commit
ae3fcda921
@ -2,6 +2,14 @@
|
|||||||
REST API Version History
|
REST API Version History
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
1.54 (Stein, master)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Added new endpoints for external ``events``:
|
||||||
|
|
||||||
|
* POST /v1/events for creating events. (This endpoint is only intended for
|
||||||
|
internal consumption.)
|
||||||
|
|
||||||
1.53 (Stein, master)
|
1.53 (Stein, master)
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ from ironic.api.controllers.v1 import allocation
|
|||||||
from ironic.api.controllers.v1 import chassis
|
from ironic.api.controllers.v1 import chassis
|
||||||
from ironic.api.controllers.v1 import conductor
|
from ironic.api.controllers.v1 import conductor
|
||||||
from ironic.api.controllers.v1 import driver
|
from ironic.api.controllers.v1 import driver
|
||||||
|
from ironic.api.controllers.v1 import event
|
||||||
from ironic.api.controllers.v1 import node
|
from ironic.api.controllers.v1 import node
|
||||||
from ironic.api.controllers.v1 import port
|
from ironic.api.controllers.v1 import port
|
||||||
from ironic.api.controllers.v1 import portgroup
|
from ironic.api.controllers.v1 import portgroup
|
||||||
@ -111,6 +112,9 @@ class V1(base.APIBase):
|
|||||||
version = version.Version
|
version = version.Version
|
||||||
"""Version discovery information."""
|
"""Version discovery information."""
|
||||||
|
|
||||||
|
events = [link.Link]
|
||||||
|
"""Links to the events resource"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert():
|
def convert():
|
||||||
v1 = V1()
|
v1 = V1()
|
||||||
@ -204,6 +208,14 @@ class V1(base.APIBase):
|
|||||||
'allocations', '',
|
'allocations', '',
|
||||||
bookmark=True)
|
bookmark=True)
|
||||||
]
|
]
|
||||||
|
if utils.allow_expose_events():
|
||||||
|
v1.events = [link.Link.make_link('self', pecan.request.public_url,
|
||||||
|
'events', ''),
|
||||||
|
link.Link.make_link('bookmark',
|
||||||
|
pecan.request.public_url,
|
||||||
|
'events', '',
|
||||||
|
bookmark=True)
|
||||||
|
]
|
||||||
v1.version = version.default_version()
|
v1.version = version.default_version()
|
||||||
return v1
|
return v1
|
||||||
|
|
||||||
@ -221,6 +233,7 @@ class Controller(rest.RestController):
|
|||||||
heartbeat = ramdisk.HeartbeatController()
|
heartbeat = ramdisk.HeartbeatController()
|
||||||
conductors = conductor.ConductorsController()
|
conductors = conductor.ConductorsController()
|
||||||
allocations = allocation.AllocationsController()
|
allocations = allocation.AllocationsController()
|
||||||
|
events = event.EventsController()
|
||||||
|
|
||||||
@expose.expose(V1)
|
@expose.expose(V1)
|
||||||
def get(self):
|
def get(self):
|
||||||
|
54
ironic/api/controllers/v1/event.py
Normal file
54
ironic/api/controllers/v1/event.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from ironic_lib import metrics_utils
|
||||||
|
from oslo_log import log
|
||||||
|
import pecan
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
|
from ironic.api.controllers.v1 import collection
|
||||||
|
from ironic.api.controllers.v1 import types
|
||||||
|
from ironic.api.controllers.v1 import utils as api_utils
|
||||||
|
from ironic.api import expose
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common import policy
|
||||||
|
|
||||||
|
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EvtCollection(collection.Collection):
|
||||||
|
"""API representation of a collection of events."""
|
||||||
|
|
||||||
|
events = [types.eventtype]
|
||||||
|
"""A list containing event dict objects"""
|
||||||
|
|
||||||
|
|
||||||
|
class EventsController(pecan.rest.RestController):
|
||||||
|
"""REST controller for Events."""
|
||||||
|
|
||||||
|
@pecan.expose()
|
||||||
|
def _lookup(self):
|
||||||
|
if not api_utils.allow_expose_events():
|
||||||
|
pecan.abort(http_client.NOT_FOUND)
|
||||||
|
|
||||||
|
@METRICS.timer('EventsController.post')
|
||||||
|
@expose.expose(None, body=EvtCollection,
|
||||||
|
status_code=http_client.NO_CONTENT)
|
||||||
|
def post(self, evts):
|
||||||
|
if not api_utils.allow_expose_events():
|
||||||
|
raise exception.NotFound()
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('baremetal:events:post', cdict, cdict)
|
||||||
|
for e in evts.events:
|
||||||
|
LOG.debug("Recieved external event: %s", e)
|
@ -404,3 +404,83 @@ class VifType(JsonType):
|
|||||||
|
|
||||||
|
|
||||||
viftype = VifType()
|
viftype = VifType()
|
||||||
|
|
||||||
|
|
||||||
|
class EventType(wtypes.UserType):
|
||||||
|
"""A simple Event type."""
|
||||||
|
|
||||||
|
basetype = wtypes.DictType
|
||||||
|
name = 'event'
|
||||||
|
|
||||||
|
def _validate_network_port_event(value):
|
||||||
|
"""Validate network port event fields.
|
||||||
|
|
||||||
|
:param value: A event dict
|
||||||
|
:returns: value
|
||||||
|
:raises: Invalid if network port event not in proper format
|
||||||
|
"""
|
||||||
|
|
||||||
|
validators = {
|
||||||
|
'port_id': UuidType.validate,
|
||||||
|
'mac_address': MacAddressType.validate,
|
||||||
|
'status': wtypes.text,
|
||||||
|
'device_id': UuidType.validate,
|
||||||
|
'binding:host_id': UuidType.validate,
|
||||||
|
'binding:vnic_type': wtypes.text
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = set(value)
|
||||||
|
net_keys = set(validators)
|
||||||
|
net_mandatory_fields = {'port_id', 'mac_address', 'status'}
|
||||||
|
|
||||||
|
# Check all keys are valid for network port event
|
||||||
|
invalid = keys.difference(EventType.mandatory_fields.union(net_keys))
|
||||||
|
if invalid:
|
||||||
|
raise exception.Invalid(_('%s are invalid keys') % (invalid))
|
||||||
|
|
||||||
|
# Check all mandatory fields for network port event is present
|
||||||
|
missing = net_mandatory_fields.difference(keys)
|
||||||
|
if missing:
|
||||||
|
raise exception.Invalid(_('Missing mandatory keys: %s')
|
||||||
|
% ', '.join(missing))
|
||||||
|
|
||||||
|
# Check all values are of expected type
|
||||||
|
for key in net_keys:
|
||||||
|
if value.get(key):
|
||||||
|
validators[key](value[key])
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
mandatory_fields = {'event'}
|
||||||
|
event_validators = {
|
||||||
|
'network.bind_port': _validate_network_port_event,
|
||||||
|
'network.unbind_port': _validate_network_port_event,
|
||||||
|
'network.delete_port': _validate_network_port_event,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value):
|
||||||
|
"""Validate the input
|
||||||
|
|
||||||
|
:param value: A event dict
|
||||||
|
:returns: value
|
||||||
|
:raises: Invalid if event not in proper format
|
||||||
|
"""
|
||||||
|
|
||||||
|
wtypes.DictType(wtypes.text, wtypes.text).validate(value)
|
||||||
|
keys = set(value)
|
||||||
|
|
||||||
|
# Check all mandatory fields are present
|
||||||
|
missing = EventType.mandatory_fields.difference(keys)
|
||||||
|
if missing:
|
||||||
|
raise exception.Invalid(_('Missing mandatory keys: %s') % missing)
|
||||||
|
|
||||||
|
# Check event is a supported event
|
||||||
|
if value['event'] not in EventType.event_validators:
|
||||||
|
raise exception.Invalid(_('%s is not a valid event.')
|
||||||
|
% value['event'])
|
||||||
|
|
||||||
|
return EventType.event_validators[value['event']](value)
|
||||||
|
|
||||||
|
|
||||||
|
eventtype = EventType()
|
||||||
|
@ -421,6 +421,7 @@ VERSIONED_FIELDS = {
|
|||||||
'owner': versions.MINOR_50_NODE_OWNER,
|
'owner': versions.MINOR_50_NODE_OWNER,
|
||||||
'description': versions.MINOR_51_NODE_DESCRIPTION,
|
'description': versions.MINOR_51_NODE_DESCRIPTION,
|
||||||
'allocation_uuid': versions.MINOR_52_ALLOCATION,
|
'allocation_uuid': versions.MINOR_52_ALLOCATION,
|
||||||
|
'events': versions.MINOR_54_EVENTS,
|
||||||
}
|
}
|
||||||
|
|
||||||
for field in V31_FIELDS:
|
for field in V31_FIELDS:
|
||||||
@ -1022,3 +1023,11 @@ def allow_port_is_smartnic():
|
|||||||
return ((pecan.request.version.minor
|
return ((pecan.request.version.minor
|
||||||
>= versions.MINOR_53_PORT_SMARTNIC)
|
>= versions.MINOR_53_PORT_SMARTNIC)
|
||||||
and objects.Port.supports_is_smartnic())
|
and objects.Port.supports_is_smartnic())
|
||||||
|
|
||||||
|
|
||||||
|
def allow_expose_events():
|
||||||
|
"""Check if accessing events endpoint is allowed.
|
||||||
|
|
||||||
|
Version 1.54 of the API added the events endpoint.
|
||||||
|
"""
|
||||||
|
return pecan.request.version.minor >= versions.MINOR_54_EVENTS
|
||||||
|
@ -91,6 +91,7 @@ BASE_VERSION = 1
|
|||||||
# v1.51: Add description to the node object.
|
# v1.51: Add description to the node object.
|
||||||
# v1.52: Add allocation API.
|
# v1.52: Add allocation API.
|
||||||
# v1.53: Add support for Smart NIC port
|
# v1.53: Add support for Smart NIC port
|
||||||
|
# v1.54: Add events support.
|
||||||
|
|
||||||
MINOR_0_JUNO = 0
|
MINOR_0_JUNO = 0
|
||||||
MINOR_1_INITIAL_VERSION = 1
|
MINOR_1_INITIAL_VERSION = 1
|
||||||
@ -146,6 +147,7 @@ MINOR_50_NODE_OWNER = 50
|
|||||||
MINOR_51_NODE_DESCRIPTION = 51
|
MINOR_51_NODE_DESCRIPTION = 51
|
||||||
MINOR_52_ALLOCATION = 52
|
MINOR_52_ALLOCATION = 52
|
||||||
MINOR_53_PORT_SMARTNIC = 53
|
MINOR_53_PORT_SMARTNIC = 53
|
||||||
|
MINOR_54_EVENTS = 54
|
||||||
|
|
||||||
# When adding another version, update:
|
# When adding another version, update:
|
||||||
# - MINOR_MAX_VERSION
|
# - MINOR_MAX_VERSION
|
||||||
@ -153,7 +155,7 @@ MINOR_53_PORT_SMARTNIC = 53
|
|||||||
# explanation of what changed in the new version
|
# explanation of what changed in the new version
|
||||||
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
||||||
|
|
||||||
MINOR_MAX_VERSION = MINOR_53_PORT_SMARTNIC
|
MINOR_MAX_VERSION = MINOR_54_EVENTS
|
||||||
|
|
||||||
# String representations of the minor and maximum versions
|
# String representations of the minor and maximum versions
|
||||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||||
|
@ -425,6 +425,14 @@ allocation_policies = [
|
|||||||
{'path': '/nodes/{node_ident}/allocation', 'method': 'DELETE'}]),
|
{'path': '/nodes/{node_ident}/allocation', 'method': 'DELETE'}]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
event_policies = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
'baremetal:events:post',
|
||||||
|
'rule:is_admin',
|
||||||
|
'Post events',
|
||||||
|
[{'path': '/events', 'method': 'POST'}])
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def list_policies():
|
def list_policies():
|
||||||
policies = itertools.chain(
|
policies = itertools.chain(
|
||||||
@ -439,6 +447,7 @@ def list_policies():
|
|||||||
volume_policies,
|
volume_policies,
|
||||||
conductor_policies,
|
conductor_policies,
|
||||||
allocation_policies,
|
allocation_policies,
|
||||||
|
event_policies
|
||||||
)
|
)
|
||||||
return policies
|
return policies
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ RELEASE_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'master': {
|
'master': {
|
||||||
'api': '1.53',
|
'api': '1.54',
|
||||||
'rpc': '1.48',
|
'rpc': '1.48',
|
||||||
'objects': {
|
'objects': {
|
||||||
'Allocation': ['1.0'],
|
'Allocation': ['1.0'],
|
||||||
|
176
ironic/tests/unit/api/controllers/v1/test_event.py
Normal file
176
ironic/tests/unit/api/controllers/v1/test_event.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""
|
||||||
|
Tests for the API /events methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
|
from ironic.api.controllers import base as api_base
|
||||||
|
from ironic.api.controllers.v1 import types
|
||||||
|
from ironic.api.controllers.v1 import versions
|
||||||
|
from ironic.tests.unit.api import base as test_api_base
|
||||||
|
from ironic.tests.unit.api.utils import fake_event_validator
|
||||||
|
|
||||||
|
|
||||||
|
def get_fake_port_event():
|
||||||
|
return {'event': 'network.bind_port',
|
||||||
|
'port_id': '11111111-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'mac_address': 'de:ad:ca:fe:ba:be',
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'device_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:host_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:vnic_type': 'baremetal'}
|
||||||
|
|
||||||
|
|
||||||
|
class TestPost(test_api_base.BaseApiTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPost, self).setUp()
|
||||||
|
self.headers = {api_base.Version.string: str(
|
||||||
|
versions.max_version_string())}
|
||||||
|
|
||||||
|
@mock.patch.object(types.EventType, 'event_validators',
|
||||||
|
{'valid.event': fake_event_validator})
|
||||||
|
def test_events(self):
|
||||||
|
events_dict = {'events': [{'event': 'valid.event'}]}
|
||||||
|
response = self.post_json('/events', events_dict, headers=self.headers)
|
||||||
|
self.assertEqual(http_client.NO_CONTENT, response.status_int)
|
||||||
|
|
||||||
|
@mock.patch.object(types.EventType, 'event_validators',
|
||||||
|
{'valid.event1': fake_event_validator,
|
||||||
|
'valid.event2': fake_event_validator,
|
||||||
|
'valid.event3': fake_event_validator})
|
||||||
|
def test_multiple_events(self):
|
||||||
|
events_dict = {'events': [{'event': 'valid.event1'},
|
||||||
|
{'event': 'valid.event2'},
|
||||||
|
{'event': 'valid.event3'}]}
|
||||||
|
response = self.post_json('/events', events_dict, headers=self.headers)
|
||||||
|
self.assertEqual(http_client.NO_CONTENT, response.status_int)
|
||||||
|
|
||||||
|
def test_events_does_not_contain_event(self):
|
||||||
|
events_dict = {'events': [{'INVALID': 'fake.event'}]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
@mock.patch.object(types.EventType, 'event_validators',
|
||||||
|
{'valid.event': fake_event_validator})
|
||||||
|
def test_events_invalid_event(self):
|
||||||
|
events_dict = {'events': [{'event': 'invalid.event'}]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_network_unknown_event_property(self):
|
||||||
|
events_dict = {'events': [{'event': 'network.unbind_port',
|
||||||
|
'UNKNOWN': 'EVENT_PROPERTY'}]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_network_bind_port_events(self):
|
||||||
|
events_dict = {'events': [get_fake_port_event()]}
|
||||||
|
response = self.post_json('/events', events_dict, headers=self.headers)
|
||||||
|
self.assertEqual(http_client.NO_CONTENT, response.status_int)
|
||||||
|
|
||||||
|
def test_network_unbind_port_events(self):
|
||||||
|
events_dict = {'events': [get_fake_port_event()]}
|
||||||
|
events_dict['events'][0].update({'event': 'network.unbind_port'})
|
||||||
|
response = self.post_json('/events', events_dict, headers=self.headers)
|
||||||
|
self.assertEqual(http_client.NO_CONTENT, response.status_int)
|
||||||
|
|
||||||
|
def test_network_delete_port_events(self):
|
||||||
|
events_dict = {'events': [get_fake_port_event()]}
|
||||||
|
events_dict['events'][0].update({'event': 'network.delete_port'})
|
||||||
|
response = self.post_json('/events', events_dict, headers=self.headers)
|
||||||
|
self.assertEqual(http_client.NO_CONTENT, response.status_int)
|
||||||
|
|
||||||
|
def test_network_port_event_invalid_mac_address(self):
|
||||||
|
port_evt = get_fake_port_event()
|
||||||
|
port_evt.update({'mac_address': 'INVALID_MAC_ADDRESS'})
|
||||||
|
events_dict = {'events': [port_evt]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_network_port_event_invalid_device_id(self):
|
||||||
|
port_evt = get_fake_port_event()
|
||||||
|
port_evt.update({'device_id': 'DEVICE_ID_SHOULD_BE_UUID'})
|
||||||
|
events_dict = {'events': [port_evt]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_network_port_event_invalid_port_id(self):
|
||||||
|
port_evt = get_fake_port_event()
|
||||||
|
port_evt.update({'port_id': 'PORT_ID_SHOULD_BE_UUID'})
|
||||||
|
events_dict = {'events': [port_evt]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_network_port_event_invalid_status(self):
|
||||||
|
port_evt = get_fake_port_event()
|
||||||
|
port_evt.update({'status': ['status', 'SHOULD', 'BE', 'TEXT']})
|
||||||
|
events_dict = {'events': [port_evt]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_network_port_event_invalid_binding_vnic_type(self):
|
||||||
|
port_evt = get_fake_port_event()
|
||||||
|
port_evt.update({'binding:vnic_type': ['binding:vnic_type', 'SHOULD',
|
||||||
|
'BE', 'TEXT']})
|
||||||
|
events_dict = {'events': [port_evt]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_network_port_event_invalid_binding_host_id(self):
|
||||||
|
port_evt = get_fake_port_event()
|
||||||
|
port_evt.update({'binding:host_id': ['binding:host_id', 'IS',
|
||||||
|
'NODE_UUID', 'IN', 'IRONIC']})
|
||||||
|
events_dict = {'events': [port_evt]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
@mock.patch.object(types.EventType, 'event_validators',
|
||||||
|
{'valid.event': fake_event_validator})
|
||||||
|
def test_events_unsupported_api_version(self):
|
||||||
|
headers = {api_base.Version.string: '1.50'}
|
||||||
|
events_dict = {'events': [{'event': 'valid.event'}]}
|
||||||
|
response = self.post_json('/events', events_dict, expect_errors=True,
|
||||||
|
headers=headers)
|
||||||
|
self.assertEqual(http_client.NOT_FOUND, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
@ -27,6 +27,7 @@ from ironic.api.controllers.v1 import types
|
|||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common import utils
|
from ironic.common import utils
|
||||||
from ironic.tests import base
|
from ironic.tests import base
|
||||||
|
from ironic.tests.unit.api.utils import fake_event_validator
|
||||||
|
|
||||||
|
|
||||||
class TestMacAddressType(base.TestCase):
|
class TestMacAddressType(base.TestCase):
|
||||||
@ -393,3 +394,60 @@ class TestVifType(base.TestCase):
|
|||||||
v = types.viftype
|
v = types.viftype
|
||||||
self.assertRaises(exception.InvalidUuidOrName,
|
self.assertRaises(exception.InvalidUuidOrName,
|
||||||
v.frombasetype, {'id': 5678})
|
v.frombasetype, {'id': 5678})
|
||||||
|
|
||||||
|
|
||||||
|
class TestEventType(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestEventType, self).setUp()
|
||||||
|
self.v = types.eventtype
|
||||||
|
|
||||||
|
@mock.patch.object(types.EventType, 'event_validators',
|
||||||
|
{'valid.event': fake_event_validator})
|
||||||
|
def test_simple_event_type(self):
|
||||||
|
value = {'event': 'valid.event'}
|
||||||
|
self.assertItemsEqual(value, self.v.validate(value))
|
||||||
|
|
||||||
|
@mock.patch.object(types.EventType, 'event_validators',
|
||||||
|
{'valid.event': fake_event_validator})
|
||||||
|
def test_invalid_event_type(self):
|
||||||
|
value = {'event': 'invalid.event'}
|
||||||
|
self.assertRaisesRegex(exception.Invalid, 'invalid.event is not a '
|
||||||
|
'valid event.',
|
||||||
|
self.v.validate, value)
|
||||||
|
|
||||||
|
def test_event_missing_madatory_field(self):
|
||||||
|
value = {'invalid': 'invalid'}
|
||||||
|
self.assertRaisesRegex(exception.Invalid, 'Missing mandatory keys:',
|
||||||
|
self.v.validate, value)
|
||||||
|
|
||||||
|
def test_network_port_event(self):
|
||||||
|
value = {'event': 'network.bind_port',
|
||||||
|
'port_id': '11111111-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'mac_address': 'de:ad:ca:fe:ba:be',
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'device_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:host_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:vnic_type': 'baremetal'
|
||||||
|
}
|
||||||
|
self.assertItemsEqual(value, self.v.validate(value))
|
||||||
|
|
||||||
|
def test_invalid_mac_network_port_event(self):
|
||||||
|
value = {'event': 'network.bind_port',
|
||||||
|
'port_id': '11111111-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'mac_address': 'INVALID_MAC_ADDRESS',
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'device_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:host_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:vnic_type': 'baremetal'
|
||||||
|
}
|
||||||
|
self.assertRaises(exception.InvalidMAC, self.v.validate, value)
|
||||||
|
|
||||||
|
def test_missing_mandatory_fields_network_port_event(self):
|
||||||
|
value = {'event': 'network.bind_port',
|
||||||
|
'device_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:host_id': '22222222-aaaa-bbbb-cccc-555555555555',
|
||||||
|
'binding:vnic_type': 'baremetal'
|
||||||
|
}
|
||||||
|
self.assertRaisesRegex(exception.Invalid, 'Missing mandatory keys:',
|
||||||
|
self.v.validate, value)
|
||||||
|
@ -195,3 +195,8 @@ def allocation_post_data(**kw):
|
|||||||
allocation = db_utils.get_test_allocation(**kw)
|
allocation = db_utils.get_test_allocation(**kw)
|
||||||
return {key: value for key, value in allocation.items()
|
return {key: value for key, value in allocation.items()
|
||||||
if key in _ALLOCATION_POST_FIELDS}
|
if key in _ALLOCATION_POST_FIELDS}
|
||||||
|
|
||||||
|
|
||||||
|
def fake_event_validator(v):
|
||||||
|
"""A fake event validator"""
|
||||||
|
return v
|
||||||
|
Loading…
x
Reference in New Issue
Block a user