Heat support for Tap-as-a-Service resources
Add heat support for Tap as a Service resources (Tap Service and Tap Flow) Change-Id: I2383e02dd3126edece2acf8425143fd3745ef454
This commit is contained in:
parent
262c432e96
commit
b9af0cf53f
@ -199,6 +199,10 @@ class NeutronClientPlugin(client_plugin.ClientPlugin):
|
||||
path = "/sfc/flow_classifiers"
|
||||
elif resource == 'port_chain':
|
||||
path = "/sfc/port_chains"
|
||||
elif resource == 'tap_service':
|
||||
path = "/taas/tap_services"
|
||||
elif resource == 'tap_flow':
|
||||
path = "/taas/tap_flows"
|
||||
return path
|
||||
|
||||
def create_ext_resource(self, resource, props):
|
||||
@ -229,6 +233,13 @@ class NeutronClientPlugin(client_plugin.ClientPlugin):
|
||||
return self.client().show_ext(path + '/%s', resource_id
|
||||
).get(resource)
|
||||
|
||||
def check_ext_resource_status(self, resource, resource_id):
|
||||
ext_resource = self.show_ext_resource(resource, resource_id)
|
||||
status = ext_resource['status']
|
||||
if status == 'ERROR':
|
||||
raise exception.ResourceInError(resource_status=status)
|
||||
return status == 'ACTIVE'
|
||||
|
||||
def resolve_ext_resource(self, resource, name_or_id):
|
||||
"""Returns the id and validate neutron ext resource."""
|
||||
|
||||
|
32
heat/engine/clients/os/neutron/taas_constraints.py
Normal file
32
heat/engine/clients/os/neutron/taas_constraints.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2018 AT&T Corporation.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
# 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 heat.engine.clients.os.neutron import neutron_constraints as nc
|
||||
|
||||
CLIENT_NAME = 'neutron'
|
||||
|
||||
|
||||
class TapServiceConstraint(nc.NeutronExtConstraint):
|
||||
resource_name = 'tap_service'
|
||||
extension = 'taas'
|
||||
|
||||
|
||||
class TapFlowConstraint(nc.NeutronExtConstraint):
|
||||
resource_name = 'tap_flow'
|
||||
extension = 'taas'
|
||||
|
||||
|
||||
class TaaSProviderConstraint(nc.ProviderConstraint):
|
||||
service_type = 'TAPASASERVICE'
|
173
heat/engine/resources/openstack/neutron/taas/tap_flow.py
Normal file
173
heat/engine/resources/openstack/neutron/taas/tap_flow.py
Normal file
@ -0,0 +1,173 @@
|
||||
# Copyright (c) 2018 AT&T Corporation.
|
||||
#
|
||||
# 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 heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine.resources.openstack.neutron import neutron
|
||||
from heat.engine import support
|
||||
from heat.engine import translation
|
||||
|
||||
COMMA_SEPARATED_LIST_REGEX = r"^([0-9]+(-[0-9]+)?)(,([0-9]+(-[0-9]+)?))*$"
|
||||
|
||||
|
||||
class TapFlow(neutron.NeutronResource):
|
||||
"""A resource for neutron tap-as-a-service tap-flow.
|
||||
|
||||
This plug-in requires neutron-taas. So to enable this
|
||||
plug-in, install this library and restart the heat-engine.
|
||||
|
||||
A Tap-Flow represents the port from which the traffic needs
|
||||
to be mirrored.
|
||||
"""
|
||||
|
||||
required_service_extension = 'taas'
|
||||
|
||||
entity = 'tap_flow'
|
||||
|
||||
support_status = support.SupportStatus(version='12.0.0')
|
||||
|
||||
PROPERTIES = (
|
||||
NAME, DESCRIPTION, PORT, TAP_SERVICE, DIRECTION,
|
||||
VLAN_FILTER
|
||||
) = (
|
||||
'name', 'description', 'port', 'tap_service', 'direction',
|
||||
'vlan_filter'
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name for the Tap-Flow.'),
|
||||
default="",
|
||||
update_allowed=True
|
||||
),
|
||||
DESCRIPTION: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Description for the Tap-Flow.'),
|
||||
default="",
|
||||
update_allowed=True
|
||||
),
|
||||
PORT: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('ID or name of the tap-flow neutron port.'),
|
||||
constraints=[constraints.CustomConstraint('neutron.port')],
|
||||
required=True,
|
||||
),
|
||||
TAP_SERVICE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('ID or name of the neutron tap-service.'),
|
||||
constraints=[
|
||||
constraints.CustomConstraint('neutron.taas.tap_service')
|
||||
],
|
||||
required=True,
|
||||
),
|
||||
DIRECTION: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('The Direction to capture the traffic on.'),
|
||||
default='BOTH',
|
||||
constraints=[
|
||||
constraints.AllowedValues(['IN', 'OUT', 'BOTH']),
|
||||
]
|
||||
),
|
||||
VLAN_FILTER: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Comma separated list of VLANs, data for which needs to be '
|
||||
'captured on probe VM.'),
|
||||
constraints=[
|
||||
constraints.AllowedPattern(COMMA_SEPARATED_LIST_REGEX),
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
def translation_rules(self, props):
|
||||
return [
|
||||
translation.TranslationRule(
|
||||
props,
|
||||
translation.TranslationRule.RESOLVE,
|
||||
[self.PORT],
|
||||
client_plugin=self.client_plugin(),
|
||||
finder='find_resourceid_by_name_or_id',
|
||||
entity='port'
|
||||
),
|
||||
translation.TranslationRule(
|
||||
props,
|
||||
translation.TranslationRule.RESOLVE,
|
||||
[self.TAP_SERVICE],
|
||||
client_plugin=self.client_plugin(),
|
||||
finder='find_resourceid_by_name_or_id',
|
||||
entity='tap_service'
|
||||
)
|
||||
]
|
||||
|
||||
def _show_resource(self):
|
||||
return self.client_plugin().show_ext_resource('tap_flow',
|
||||
self.resource_id)
|
||||
|
||||
def handle_create(self):
|
||||
props = self.prepare_properties(self.properties,
|
||||
self.physical_resource_name())
|
||||
props['source_port'] = props.pop(self.PORT)
|
||||
props['tap_service_id'] = props.pop(self.TAP_SERVICE)
|
||||
tap_flow = self.client_plugin().create_ext_resource('tap_flow',
|
||||
props)
|
||||
self.resource_id_set(tap_flow['id'])
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
if prop_diff:
|
||||
self.prepare_update_properties(prop_diff)
|
||||
self.client_plugin().update_ext_resource('tap_flow', prop_diff,
|
||||
self.resource_id)
|
||||
|
||||
def handle_delete(self):
|
||||
if self.resource_id is None:
|
||||
return
|
||||
with self.client_plugin().ignore_not_found:
|
||||
self.client_plugin().delete_ext_resource('tap_flow',
|
||||
self.resource_id)
|
||||
|
||||
def check_create_complete(self, data):
|
||||
return self.client_plugin().check_ext_resource_status(
|
||||
'tap_flow', self.resource_id)
|
||||
|
||||
def check_update_complete(self, prop_diff):
|
||||
if prop_diff:
|
||||
return self.client_plugin().check_ext_resource_status(
|
||||
'tap_flow', self.resource_id)
|
||||
return True
|
||||
|
||||
def check_delete_complete(self, data):
|
||||
if self.resource_id is None:
|
||||
return True
|
||||
|
||||
with self.client_plugin().ignore_not_found:
|
||||
try:
|
||||
if self.client_plugin().check_ext_resource_status(
|
||||
'tap_flow', self.resource_id):
|
||||
self.client_plugin().delete_ext_resource(
|
||||
'tap_flow', self.resource_id)
|
||||
except exception.ResourceInError:
|
||||
# Still try to delete tap resource in error state
|
||||
self.client_plugin().delete_ext_resource('tap_flow',
|
||||
self.resource_id)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
return {
|
||||
'OS::Neutron::TaaS::TapFlow': TapFlow,
|
||||
}
|
137
heat/engine/resources/openstack/neutron/taas/tap_service.py
Normal file
137
heat/engine/resources/openstack/neutron/taas/tap_service.py
Normal file
@ -0,0 +1,137 @@
|
||||
# Copyright (c) 2018 AT&T Corporation.
|
||||
#
|
||||
# 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 heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine.resources.openstack.neutron import neutron
|
||||
from heat.engine import support
|
||||
from heat.engine import translation
|
||||
|
||||
|
||||
class TapService(neutron.NeutronResource):
|
||||
"""A resource for neutron tap-as-a-service tap-service.
|
||||
|
||||
This plug-in requires neutron-taas. So to enable this
|
||||
plug-in, install this library and restart the heat-engine.
|
||||
|
||||
A Tap-Service represents the port on which the mirrored traffic is
|
||||
delivered. Any VM that uses the mirrored data is attached to this port.
|
||||
"""
|
||||
|
||||
required_service_extension = 'taas'
|
||||
|
||||
entity = 'tap_service'
|
||||
|
||||
support_status = support.SupportStatus(version='12.0.0')
|
||||
|
||||
PROPERTIES = (
|
||||
NAME, DESCRIPTION, PORT,
|
||||
) = (
|
||||
'name', 'description', 'port',
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name for the Tap-Service.'),
|
||||
default="",
|
||||
update_allowed=True
|
||||
),
|
||||
DESCRIPTION: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Description for the Tap-Service.'),
|
||||
default="",
|
||||
update_allowed=True
|
||||
),
|
||||
PORT: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('ID or name of the tap-service neutron port.'),
|
||||
constraints=[constraints.CustomConstraint('neutron.port')],
|
||||
required=True,
|
||||
),
|
||||
}
|
||||
|
||||
def translation_rules(self, props):
|
||||
return [
|
||||
translation.TranslationRule(
|
||||
props,
|
||||
translation.TranslationRule.RESOLVE,
|
||||
[self.PORT],
|
||||
client_plugin=self.client_plugin(),
|
||||
finder='find_resourceid_by_name_or_id',
|
||||
entity='port'
|
||||
)
|
||||
]
|
||||
|
||||
def _show_resource(self):
|
||||
return self.client_plugin().show_ext_resource('tap_service',
|
||||
self.resource_id)
|
||||
|
||||
def handle_create(self):
|
||||
props = self.prepare_properties(self.properties,
|
||||
self.physical_resource_name())
|
||||
props['port_id'] = props.pop(self.PORT)
|
||||
ts = self.client_plugin().create_ext_resource('tap_service',
|
||||
props)
|
||||
self.resource_id_set(ts['id'])
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
if prop_diff:
|
||||
self.prepare_update_properties(prop_diff)
|
||||
self.client_plugin().update_ext_resource('tap_service',
|
||||
prop_diff,
|
||||
self.resource_id)
|
||||
|
||||
def handle_delete(self):
|
||||
if self.resource_id is None:
|
||||
return
|
||||
with self.client_plugin().ignore_not_found:
|
||||
self.client_plugin().delete_ext_resource('tap_service',
|
||||
self.resource_id)
|
||||
|
||||
def check_create_complete(self, data):
|
||||
return self.client_plugin().check_ext_resource_status(
|
||||
'tap_service', self.resource_id)
|
||||
|
||||
def check_update_complete(self, prop_diff):
|
||||
if prop_diff:
|
||||
return self.client_plugin().check_ext_resource_status(
|
||||
'tap_service', self.resource_id)
|
||||
return True
|
||||
|
||||
def check_delete_complete(self, data):
|
||||
if self.resource_id is None:
|
||||
return True
|
||||
|
||||
with self.client_plugin().ignore_not_found:
|
||||
try:
|
||||
if self.client_plugin().check_ext_resource_status(
|
||||
'tap_service', self.resource_id):
|
||||
self.client_plugin().delete_ext_resource(
|
||||
'tap_service', self.resource_id)
|
||||
except exception.ResourceInError:
|
||||
# Still try to delete tap resource in error state
|
||||
self.client_plugin().delete_ext_resource('tap_service',
|
||||
self.resource_id)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
return {
|
||||
'OS::Neutron::TaaS::TapService': TapService,
|
||||
}
|
180
heat/tests/openstack/neutron/test_taas/test_tap_flow.py
Normal file
180
heat/tests/openstack/neutron/test_taas/test_tap_flow.py
Normal file
@ -0,0 +1,180 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from heat.engine.resources.openstack.neutron.taas import tap_flow
|
||||
from heat.engine import stack
|
||||
from heat.engine import template
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
sample_template = {
|
||||
'heat_template_version': '2016-04-08',
|
||||
'resources': {
|
||||
'test_resource': {
|
||||
'type': 'OS::Neutron::TaaS::TapFlow',
|
||||
'properties': {
|
||||
'name': 'test_tap_flow',
|
||||
'description': 'desc',
|
||||
'port': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
'tap_service': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
'direction': 'BOTH',
|
||||
'vlan_filter': '1-5,9,18,27-30,99-108,4000-4095'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RESOURCE_TYPE = 'OS::Neutron::TaaS::TapFlow'
|
||||
|
||||
|
||||
class TapFlowTest(common.HeatTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TapFlowTest, self).setUp()
|
||||
|
||||
self.ctx = utils.dummy_context()
|
||||
|
||||
self.stack = stack.Stack(
|
||||
self.ctx, 'test_stack',
|
||||
template.Template(sample_template)
|
||||
)
|
||||
self.test_resource = self.stack['test_resource']
|
||||
|
||||
self.test_client_plugin = mock.MagicMock()
|
||||
self.test_resource.client_plugin = mock.MagicMock(
|
||||
return_value=self.test_client_plugin)
|
||||
|
||||
self.test_client = mock.MagicMock()
|
||||
self.test_resource.client = mock.MagicMock(
|
||||
return_value=self.test_client)
|
||||
|
||||
self.test_client_plugin.get_notification = mock.MagicMock(
|
||||
return_value='sample_notification')
|
||||
|
||||
def test_resource_mapping(self):
|
||||
mapping = tap_flow.resource_mapping()
|
||||
self.assertEqual(
|
||||
tap_flow.TapFlow,
|
||||
mapping['OS::Neutron::TaaS::TapFlow'])
|
||||
|
||||
def _get_mock_resource(self):
|
||||
value = mock.MagicMock()
|
||||
value.id = '477e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
return value
|
||||
|
||||
def test_resource_handle_create(self):
|
||||
mock_tap_flow_create = self.test_client_plugin.create_ext_resource
|
||||
mock_resource = self._get_mock_resource()
|
||||
mock_tap_flow_create.return_value = mock_resource
|
||||
|
||||
# validate the properties
|
||||
self.assertEqual(
|
||||
'test_tap_flow',
|
||||
self.test_resource.properties.get(
|
||||
tap_flow.TapFlow.NAME))
|
||||
self.assertEqual(
|
||||
'desc',
|
||||
self.test_resource.properties.get(
|
||||
tap_flow.TapFlow.DESCRIPTION))
|
||||
self.assertEqual(
|
||||
'6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
self.test_resource.properties.get(
|
||||
tap_flow.TapFlow.PORT))
|
||||
self.assertEqual(
|
||||
'6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
self.test_resource.properties.get(
|
||||
tap_flow.TapFlow.TAP_SERVICE))
|
||||
self.assertEqual(
|
||||
'BOTH',
|
||||
self.test_resource.properties.get(
|
||||
tap_flow.TapFlow.DIRECTION))
|
||||
self.assertEqual(
|
||||
'1-5,9,18,27-30,99-108,4000-4095',
|
||||
self.test_resource.properties.get(
|
||||
tap_flow.TapFlow.VLAN_FILTER))
|
||||
|
||||
self.test_resource.data_set = mock.Mock()
|
||||
self.test_resource.handle_create()
|
||||
mock_tap_flow_create.assert_called_once_with(
|
||||
'tap_flow',
|
||||
{
|
||||
'name': 'test_tap_flow',
|
||||
'description': 'desc',
|
||||
'port': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
'tap_service': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
'direction': 'BOTH',
|
||||
'vlan_filter': '1-5,9,18,27-30,99-108,4000-4095',
|
||||
}
|
||||
)
|
||||
|
||||
def test_resource_handle_delete(self):
|
||||
mock_tap_flow_delete = self.test_client_plugin.delete_ext_resource
|
||||
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
mock_tap_flow_delete.return_value = None
|
||||
self.assertIsNone(self.test_resource.handle_delete())
|
||||
mock_tap_flow_delete.assert_called_once_with(
|
||||
'tap_flow', self.test_resource.resource_id)
|
||||
|
||||
def test_resource_handle_delete_resource_id_is_none(self):
|
||||
self.test_resource.resource_id = None
|
||||
self.assertIsNone(self.test_resource.handle_delete())
|
||||
self.assertEqual(0, self.test_client_plugin.
|
||||
delete_ext_resource.call_count)
|
||||
|
||||
def test_resource_handle_delete_not_found(self):
|
||||
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
mock_tap_flow_delete = self.test_client_plugin.delete_ext_resource
|
||||
mock_tap_flow_delete.side_effect = self.test_client_plugin.NotFound
|
||||
self.assertIsNone(self.test_resource.handle_delete())
|
||||
|
||||
def test_resource_show_resource(self):
|
||||
mock_tap_flow_get = self.test_client_plugin.show_ext_resource
|
||||
mock_tap_flow_get.return_value = {}
|
||||
self.assertEqual({},
|
||||
self.test_resource._show_resource(),
|
||||
'Failed to show resource')
|
||||
|
||||
def test_resource_handle_update(self):
|
||||
mock_tap_flow_patch = self.test_client_plugin.update_ext_resource
|
||||
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
|
||||
prop_diff = {
|
||||
tap_flow.TapFlow.NAME:
|
||||
'name-updated',
|
||||
tap_flow.TapFlow.DESCRIPTION:
|
||||
'description-updated',
|
||||
tap_flow.TapFlow.PORT:
|
||||
'6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
tap_flow.TapFlow.TAP_SERVICE:
|
||||
'6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
tap_flow.TapFlow.DIRECTION:
|
||||
'BOTH',
|
||||
tap_flow.TapFlow.VLAN_FILTER:
|
||||
'1-5,9,18,27-30,99-108,4000-4095'
|
||||
}
|
||||
self.test_resource.handle_update(json_snippet=None,
|
||||
tmpl_diff=None,
|
||||
prop_diff=prop_diff)
|
||||
|
||||
mock_tap_flow_patch.assert_called_once_with(
|
||||
'tap_flow',
|
||||
{
|
||||
'name': 'name-updated',
|
||||
'description': 'description-updated',
|
||||
'port': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
'tap_service': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
'direction': 'BOTH',
|
||||
'vlan_filter': '1-5,9,18,27-30,99-108,4000-4095',
|
||||
}, self.test_resource.resource_id)
|
152
heat/tests/openstack/neutron/test_taas/test_tap_service.py
Normal file
152
heat/tests/openstack/neutron/test_taas/test_tap_service.py
Normal file
@ -0,0 +1,152 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from heat.engine.resources.openstack.neutron.taas import tap_service
|
||||
from heat.engine import stack
|
||||
from heat.engine import template
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
sample_template = {
|
||||
'heat_template_version': '2016-04-08',
|
||||
'resources': {
|
||||
'test_resource': {
|
||||
'type': 'OS::Neutron::TaaS::TapService',
|
||||
'properties': {
|
||||
'name': 'test_tap_service',
|
||||
'description': 'desc',
|
||||
'port': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RESOURCE_TYPE = 'OS::Neutron::TaaS::TapService'
|
||||
|
||||
|
||||
class TapServiceTest(common.HeatTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TapServiceTest, self).setUp()
|
||||
|
||||
self.ctx = utils.dummy_context()
|
||||
|
||||
self.stack = stack.Stack(
|
||||
self.ctx, 'test_stack',
|
||||
template.Template(sample_template)
|
||||
)
|
||||
self.test_resource = self.stack['test_resource']
|
||||
|
||||
self.test_client_plugin = mock.MagicMock()
|
||||
self.test_resource.client_plugin = mock.MagicMock(
|
||||
return_value=self.test_client_plugin)
|
||||
|
||||
self.test_client = mock.MagicMock()
|
||||
self.test_resource.client = mock.MagicMock(
|
||||
return_value=self.test_client)
|
||||
|
||||
self.test_client_plugin.get_notification = mock.MagicMock(
|
||||
return_value='sample_notification')
|
||||
|
||||
def test_resource_mapping(self):
|
||||
mapping = tap_service.resource_mapping()
|
||||
self.assertEqual(tap_service.TapService,
|
||||
mapping['OS::Neutron::TaaS::TapService'])
|
||||
|
||||
def _get_mock_resource(self):
|
||||
value = mock.MagicMock()
|
||||
value.id = '477e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
return value
|
||||
|
||||
def test_resource_handle_create(self):
|
||||
mock_tap_service_create = self.test_client_plugin.create_ext_resource
|
||||
mock_resource = self._get_mock_resource()
|
||||
mock_tap_service_create.return_value = mock_resource
|
||||
|
||||
# validate the properties
|
||||
self.assertEqual(
|
||||
'test_tap_service',
|
||||
self.test_resource.properties.get(
|
||||
tap_service.TapService.NAME))
|
||||
self.assertEqual(
|
||||
'desc',
|
||||
self.test_resource.properties.get(
|
||||
tap_service.TapService.DESCRIPTION))
|
||||
self.assertEqual(
|
||||
'6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
self.test_resource.properties.get(
|
||||
tap_service.TapService.PORT))
|
||||
|
||||
self.test_resource.data_set = mock.Mock()
|
||||
self.test_resource.handle_create()
|
||||
mock_tap_service_create.assert_called_once_with(
|
||||
'tap_service',
|
||||
{
|
||||
'name': 'test_tap_service',
|
||||
'description': 'desc',
|
||||
'port': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
}
|
||||
)
|
||||
|
||||
def test_resource_handle_delete(self):
|
||||
mock_tap_service_delete = self.test_client_plugin.delete_ext_resource
|
||||
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
mock_tap_service_delete.return_value = None
|
||||
self.assertIsNone(self.test_resource.handle_delete())
|
||||
mock_tap_service_delete.assert_called_once_with(
|
||||
'tap_service', self.test_resource.resource_id)
|
||||
|
||||
def test_resource_handle_delete_resource_id_is_none(self):
|
||||
self.test_resource.resource_id = None
|
||||
self.assertIsNone(self.test_resource.handle_delete())
|
||||
self.assertEqual(0, self.test_client_plugin.
|
||||
delete_ext_resource.call_count)
|
||||
|
||||
def test_resource_handle_delete_not_found(self):
|
||||
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
mock_tap_service_delete = self.test_client_plugin.delete_ext_resource
|
||||
mock_tap_service_delete.side_effect = self.test_client_plugin.NotFound
|
||||
self.assertIsNone(self.test_resource.handle_delete())
|
||||
|
||||
def test_resource_show_resource(self):
|
||||
mock_tap_service_get = self.test_client_plugin.show_ext_resource
|
||||
mock_tap_service_get.return_value = {}
|
||||
self.assertEqual({},
|
||||
self.test_resource._show_resource(),
|
||||
'Failed to show resource')
|
||||
|
||||
def test_resource_handle_update(self):
|
||||
mock_tap_service_patch = self.test_client_plugin.update_ext_resource
|
||||
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
|
||||
prop_diff = {
|
||||
tap_service.TapService.NAME:
|
||||
'name-updated',
|
||||
tap_service.TapService.DESCRIPTION:
|
||||
'description-updated',
|
||||
tap_service.TapService.PORT:
|
||||
'6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
}
|
||||
self.test_resource.handle_update(json_snippet=None,
|
||||
tmpl_diff=None,
|
||||
prop_diff=prop_diff)
|
||||
|
||||
mock_tap_service_patch.assert_called_once_with(
|
||||
'tap_service',
|
||||
{
|
||||
'name': 'name-updated',
|
||||
'description': 'description-updated',
|
||||
'port': '6af055d3-26f6-48dd-a597-7611d7e58d35',
|
||||
}, self.test_resource.resource_id)
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
A new ``OS::Neutron::TaaS::TapService`` resource is added to support a
|
||||
Tap Service in the Neutron Tap-as-a-service plugin.
|
||||
- |
|
||||
A new ``OS::Neutron::TaaS::TapFlow`` resource is added to support a Tap
|
||||
Flow in the Neutron Tap-as-a-service plugin.
|
@ -142,6 +142,8 @@ heat.constraints =
|
||||
neutron.segment = heat.engine.clients.os.openstacksdk:SegmentConstraint
|
||||
neutron.subnet = heat.engine.clients.os.neutron.neutron_constraints:SubnetConstraint
|
||||
neutron.subnetpool = heat.engine.clients.os.neutron.neutron_constraints:SubnetPoolConstraint
|
||||
neutron.taas.tap_service = heat.engine.clients.os.neutron.taas_constraints:TapServiceConstraint
|
||||
neutron.taas.tap_flow = heat.engine.clients.os.neutron.taas_constraints:TapFlowConstraint
|
||||
nova.flavor = heat.engine.clients.os.nova:FlavorConstraint
|
||||
nova.host = heat.engine.clients.os.nova:HostConstraint
|
||||
nova.keypair = heat.engine.clients.os.nova:KeypairConstraint
|
||||
|
Loading…
Reference in New Issue
Block a user