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"
|
path = "/sfc/flow_classifiers"
|
||||||
elif resource == 'port_chain':
|
elif resource == 'port_chain':
|
||||||
path = "/sfc/port_chains"
|
path = "/sfc/port_chains"
|
||||||
|
elif resource == 'tap_service':
|
||||||
|
path = "/taas/tap_services"
|
||||||
|
elif resource == 'tap_flow':
|
||||||
|
path = "/taas/tap_flows"
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def create_ext_resource(self, resource, props):
|
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
|
return self.client().show_ext(path + '/%s', resource_id
|
||||||
).get(resource)
|
).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):
|
def resolve_ext_resource(self, resource, name_or_id):
|
||||||
"""Returns the id and validate neutron ext resource."""
|
"""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.segment = heat.engine.clients.os.openstacksdk:SegmentConstraint
|
||||||
neutron.subnet = heat.engine.clients.os.neutron.neutron_constraints:SubnetConstraint
|
neutron.subnet = heat.engine.clients.os.neutron.neutron_constraints:SubnetConstraint
|
||||||
neutron.subnetpool = heat.engine.clients.os.neutron.neutron_constraints:SubnetPoolConstraint
|
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.flavor = heat.engine.clients.os.nova:FlavorConstraint
|
||||||
nova.host = heat.engine.clients.os.nova:HostConstraint
|
nova.host = heat.engine.clients.os.nova:HostConstraint
|
||||||
nova.keypair = heat.engine.clients.os.nova:KeypairConstraint
|
nova.keypair = heat.engine.clients.os.nova:KeypairConstraint
|
||||||
|
Loading…
Reference in New Issue
Block a user