Adding Create Port Functionality for Cue Backend using TaskFlow

* Create port uses neutron client
* Added unit tests for new functionality

* Added a neutron test fixture

Change-Id: Ia0f5d35d317726cd3ad22e4c5e1a34ae9eb65ddc
This commit is contained in:
Vipul Sabhaya 2015-01-27 15:58:45 -08:00 committed by Min Pae
parent 0b67215676
commit 35d8d42738
8 changed files with 280 additions and 7 deletions

View File

@ -110,6 +110,8 @@ class StubOutForTesting(object):
class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""
additional_fixtures = []
def setUp(self):
"""Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
@ -149,6 +151,12 @@ class TestCase(base.BaseTestCase):
self.session = db_api.get_session()
self._additional_fixtures = []
for fixture_cls in self.additional_fixtures:
fixture = fixture_cls()
self.useFixture(fixture)
self._additional_fixtures.append(fixture)
def tearDown(self):
"""Runs after each test method to tear down test environment."""
super(TestCase, self).tearDown()

View File

View File

View File

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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 uuid
from cue import client
from cue.tests import base
import cue.tests.test_fixtures.neutron
import os_tasklib.neutron as neutron_task
from neutronclient.common import exceptions
from taskflow import engines
from taskflow.patterns import linear_flow
SHARED_CONF = {
'connection': 'zookeeper',
}
class CreatePortTests(base.TestCase):
additional_fixtures = [
cue.tests.test_fixtures.neutron.NeutronClient
]
task_store = {
'network_id': "0",
'port_name': "port_0",
}
def test_create_port_invalid_network(self):
# retrieve neutron client API class
neutron_client = client.neutron_client()
# create flow with "CreatePort" task
flow = linear_flow.Flow('create port').add(neutron_task.CreatePort(
os_client=neutron_client, provides='neutron_port_id'))
# generate a new UUID for an 'invalid' network_id
CreatePortTests.task_store['network_id'] = str(uuid.uuid4())
try:
engines.run(flow, store=CreatePortTests.task_store)
except exceptions.NetworkNotFoundClient:
"""this exception is expected for this test"""
except Exception as e:
self.fail("Unexpected exception was thrown: " + e.message)
else:
self.fail("NetworkNotFoundClient exception not thrown as expected")
def test_create_port(self):
# retrieve neutron client API class
neutron_client = client.neutron_client()
# retrieve networks
#network_list = neutron_client.list_networks()
#self.assertNotEqual(len(network_list['networks']), 0,
# "No networks found")
# set a network_id and unique name to use
#network_id = network_list['networks'][0]['id']
network = neutron_client.create_network()
network_id = network['network']['id']
CreatePortTests.task_store['network_id'] = network_id
CreatePortTests.task_store['port_name'] = "port_" + str(uuid.uuid4())
# create flow with "CreatePort" task, given neutron client
flow = linear_flow.Flow('create port').add(neutron_task.CreatePort(
os_client=neutron_client, provides='neutron_port_id'))
# execute flow with parameters required for "CreatePort" task
engines.run(flow, store=CreatePortTests.task_store)
# retrieve list of ports from Neutron service
port_list = neutron_client.list_ports()
# find our newly created port
found = False
for port in port_list['ports']:
if port['network_id'] == CreatePortTests.task_store['network_id']:
if port['name'] == CreatePortTests.task_store['port_name']:
found = True
break
self.assertEqual(True, found, "New port was not found")

View File

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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 fixtures
import mock
class BaseFixture(fixtures.Fixture):
"""Base class for test fixtures
To create a test fixture, create a subclass of BaseFixture and define
:meth:`setUp`
Use :meth:`mock` to mock classes and map new methods to the mock'd class.
"""
def mock(self, cls):
"""Convenience method for mock'ing classes.
The class specified by :param:`cls` will be mock'd and returned to the
caller. Upon tearing down the fixture, the class will be reverted to
its original representation.
To mock a class, pass in the string path to the class, and save the
returned value to a variable. The mock'd version of the class that is
returned will be used to redefine the class' behavior. ::
mocked_class = mock('package.SomeClass')
To replace a method in the mocked class, using a member method of the
fixture allows the mock implementation to persist and share data
between methods by using fixture member variables. ::
mocked_class.method1 = self.method1
See :meth:`cue.tests.test_fixtures.neutron.NeutronClient.setUp` for an
example.
:param cls: Class to be mock'd. Pass in the string path to the class.
:return: A mock'd version of the class.
"""
patch = mock.patch(cls)
mock_instance = patch.__enter__()
self.addCleanup(patch.__exit__)
mocked_cls = mock_instance.return_value
return mocked_cls

View File

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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 uuid
from neutronclient.common import exceptions
import neutronclient.neutron.client
import cue.tests.test_fixtures.base as base
class NeutronClient(base.BaseFixture):
"""A test fixture to simulate a Neutron Client connection
This class is used in test cases to simulate a real Neutron Client
connection in the absence of a working Neutron API endpoint.
"""
def setUp(self):
"""Set up test fixture and apply all method overrides."""
super(NeutronClient, self).setUp()
self._network_list = {}
self._port_list = {}
v2_client = self.mock(neutronclient.neutron.client.API_VERSIONS['2.0'])
v2_client.create_port = self.create_port
v2_client.create_network = self.create_network
v2_client.list_ports = self.list_ports
def create_port(self, body=None):
"""Mock'd version of neutronclient...create_port().
Create a Neutron network port.
:param body: Dictionary with port information.
:return: An updated copy of the 'body' that was passed in, with other
information populated.
"""
if body and body['port'] and body['port']['network_id']:
network_id = body['port']['network_id']
if network_id not in self._network_list:
raise exceptions.NetworkNotFoundClient(
"network %s not found" % network_id
)
else:
body = {'port': {}}
port_id = uuid.uuid4()
body['port']['id'] = port_id
self._port_list[port_id] = body['port']
return body
def create_network(self, body=None):
"""Mock'd version of neutronclient...create_network().
Create a Neutron network.
:param body: Dictionary with network information.
:return: An updated copy of the 'body' that was passed in, with other
information populated.
"""
if body and body['network']:
pass
else:
body = {'network': {}}
network_id = uuid.uuid4()
body['network'].update({
'id': network_id,
'subnets': [],
'tenant_id': network_id,
'segments': [],
'shared': False,
'port_security_enabled': True,
})
self._network_list[network_id] = body
return body
def list_ports(self, retrieve_all=True, **_params):
"""Mock'd version of neutronclient...list_ports().
List available ports.
:param retrieve_all: Set to true to retrieve all available ports
"""
if retrieve_all:
return {'ports': self._port_list.values()}

View File

@ -13,18 +13,28 @@
# License for the specific language governing permissions and limitations
# under the License.
__author__ = 'sputnik13'
import os_tasklib
class CreatePort(os_tasklib.BaseTask):
default_provides = 'neutron_port_id'
def execute(self, **kwargs):
print("Create Neutron Port")
return "RANDOM_NEUTRON_PORT_ID"
def execute(self, network_id, port_name, **kwargs):
body_value = {
"port": {
"admin_state_up": True,
"name": port_name,
"network_id": network_id,
}
}
port = self.os_client.create_port(body=body_value)
port_id = port['port']['id']
return port_id
def revert(self, **kwargs):
print("Delete Neutron Port %s" % kwargs['result'])
"""Revert function for a failed create port task."""
# TODO(dagnello): no action required for revert of a failed port create
# task, but logging should be added with a flow transaction ID which
# will provide context and state to the error.