Organize agent extensions

Move extensions under an ironic_python_agent.extensions module. This
change also moves the @async_command() decorator into the base
extension module.

Change-Id: I4021fcc33a30f3460a31bca44a4bf776cd53d488
This commit is contained in:
Russell Haering 2014-04-11 16:46:36 -07:00
parent 2960f71c64
commit 5ebd2e9797
15 changed files with 59 additions and 71 deletions

View File

@ -21,9 +21,9 @@ from stevedore import extension
from wsgiref import simple_server from wsgiref import simple_server
from ironic_python_agent.api import app from ironic_python_agent.api import app
from ironic_python_agent import base
from ironic_python_agent import encoding from ironic_python_agent import encoding
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent.extensions import base
from ironic_python_agent import hardware from ironic_python_agent import hardware
from ironic_python_agent import ironic_api_client from ironic_python_agent import ironic_api_client
from ironic_python_agent.openstack.common import log from ironic_python_agent.openstack.common import log

View File

@ -1,41 +0,0 @@
# Copyright 2013 Rackspace, Inc.
#
# 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 functools
from ironic_python_agent import base
def async_command(validator=None):
"""Will run the command in an AsyncCommandResult in its own thread.
command_name is set based on the func name and command_params will
be whatever args/kwargs you pass into the decorated command.
"""
def async_decorator(func):
@functools.wraps(func)
def wrapper(self, command_name, **command_params):
# Run a validator before passing everything off to async.
# validators should raise exceptions or return silently.
if validator:
validator(self, **command_params)
# bind self to func so that AsyncCommandResult doesn't need to
# know about the mode
bound_func = functools.partial(func, self)
return base.AsyncCommandResult(command_name,
command_params,
bound_func).start()
return wrapper
return async_decorator

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import functools
import threading import threading
import uuid import uuid
@ -192,3 +193,27 @@ class ExecuteCommandMixin(object):
self.command_results[result.id] = result self.command_results[result.id] = result
return result return result
def async_command(validator=None):
"""Will run the command in an AsyncCommandResult in its own thread.
command_name is set based on the func name and command_params will
be whatever args/kwargs you pass into the decorated command.
"""
def async_decorator(func):
@functools.wraps(func)
def wrapper(self, command_name, **command_params):
# Run a validator before passing everything off to async.
# validators should raise exceptions or return silently.
if validator:
validator(self, **command_params)
# bind self to func so that AsyncCommandResult doesn't need to
# know about the mode
bound_func = functools.partial(func, self)
return AsyncCommandResult(command_name,
command_params,
bound_func).start()
return wrapper
return async_decorator

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from ironic_python_agent import base from ironic_python_agent.extensions import base
class DecomExtension(base.BaseAgentExtension): class DecomExtension(base.BaseAgentExtension):

View File

@ -14,9 +14,8 @@
from stevedore import enabled from stevedore import enabled
from ironic_python_agent import base
from ironic_python_agent import decorators
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent.extensions import base
from ironic_python_agent.openstack.common import log from ironic_python_agent.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -51,7 +50,7 @@ class FlowExtension(base.BaseAgentExtension, base.ExecuteCommandMixin):
propagate_map_exceptions=True, propagate_map_exceptions=True,
) )
@decorators.async_command(_validate_exts) @base.async_command(_validate_exts)
def start_flow(self, command_name, flow=None): def start_flow(self, command_name, flow=None):
for task in flow: for task in flow:
for method, params in task.items(): for method, params in task.items():

View File

@ -18,10 +18,9 @@ import requests
import subprocess import subprocess
import time import time
from ironic_python_agent import base
from ironic_python_agent import configdrive from ironic_python_agent import configdrive
from ironic_python_agent import decorators
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent.extensions import base
from ironic_python_agent import hardware from ironic_python_agent import hardware
from ironic_python_agent.openstack.common import log from ironic_python_agent.openstack.common import log
@ -160,7 +159,7 @@ class StandbyExtension(base.BaseAgentExtension):
self.cached_image_id = None self.cached_image_id = None
@decorators.async_command(_validate_image_info) @base.async_command(_validate_image_info)
def cache_image(self, command_name, image_info=None, force=False): def cache_image(self, command_name, image_info=None, force=False):
device = hardware.get_manager().get_os_install_device() device = hardware.get_manager().get_os_install_device()
@ -169,7 +168,7 @@ class StandbyExtension(base.BaseAgentExtension):
_write_image(image_info, device) _write_image(image_info, device)
self.cached_image_id = image_info['id'] self.cached_image_id = image_info['id']
@decorators.async_command(_validate_image_info) @base.async_command(_validate_image_info)
def prepare_image(self, def prepare_image(self,
command_name, command_name,
image_info=None, image_info=None,
@ -188,7 +187,7 @@ class StandbyExtension(base.BaseAgentExtension):
configdrive.write_configdrive(location, metadata, files) configdrive.write_configdrive(location, metadata, files)
_copy_configdrive_to_disk(location, device) _copy_configdrive_to_disk(location, device)
@decorators.async_command() @base.async_command()
def run_image(self, command_name): def run_image(self, command_name):
script = _path_to_script('shell/reboot.sh') script = _path_to_script('shell/reboot.sh')
LOG.info('Rebooting system') LOG.info('Rebooting system')

View File

@ -22,10 +22,10 @@ import six
from wsgiref import simple_server from wsgiref import simple_server
from ironic_python_agent import agent from ironic_python_agent import agent
from ironic_python_agent import base
from ironic_python_agent.cmd import agent as agent_cmd from ironic_python_agent.cmd import agent as agent_cmd
from ironic_python_agent import encoding from ironic_python_agent import encoding
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent.extensions import base
from ironic_python_agent import hardware from ironic_python_agent import hardware
EXPECTED_ERROR = RuntimeError('command execution failed') EXPECTED_ERROR = RuntimeError('command execution failed')

View File

@ -20,7 +20,7 @@ import pecan
import pecan.testing import pecan.testing
from ironic_python_agent import agent from ironic_python_agent import agent
from ironic_python_agent import base from ironic_python_agent.extensions import base
PATH_PREFIX = '/v1' PATH_PREFIX = '/v1'

View File

@ -16,8 +16,8 @@ import mock
from oslotest import base as test_base from oslotest import base as test_base
from stevedore import extension from stevedore import extension
from ironic_python_agent import base
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent.extensions import base
class FakeExtension(base.BaseAgentExtension): class FakeExtension(base.BaseAgentExtension):

View File

@ -14,7 +14,7 @@
from oslotest import base as test_base from oslotest import base as test_base
from ironic_python_agent import decom from ironic_python_agent.extensions import decom
class TestDecomExtension(test_base.BaseTestCase): class TestDecomExtension(test_base.BaseTestCase):

View File

@ -19,10 +19,9 @@ from oslotest import base as test_base
from stevedore import enabled from stevedore import enabled
from stevedore import extension from stevedore import extension
from ironic_python_agent import base
from ironic_python_agent import decorators
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent import flow from ironic_python_agent.extensions import base
from ironic_python_agent.extensions import flow
FLOW_INFO = [ FLOW_INFO = [
@ -42,7 +41,7 @@ class FakeExtension(base.BaseAgentExtension):
self.command_map['sleep'] = self.sleep self.command_map['sleep'] = self.sleep
self.command_map['sync_sleep'] = self.sync_sleep self.command_map['sync_sleep'] = self.sync_sleep
@decorators.async_command() @base.async_command()
def sleep(self, command_name, sleep_info=None): def sleep(self, command_name, sleep_info=None):
time.sleep(sleep_info['time']) time.sleep(sleep_info['time'])

View File

@ -17,7 +17,7 @@ from oslotest import base as test_base
import six import six
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent import standby from ironic_python_agent.extensions import standby
if six.PY2: if six.PY2:
OPEN_FUNCTION_NAME = '__builtin__.open' OPEN_FUNCTION_NAME = '__builtin__.open'
@ -182,7 +182,8 @@ class TestStandbyExtension(test_base.BaseTestCase):
standby._download_image, standby._download_image,
image_info) image_info)
@mock.patch('ironic_python_agent.standby._verify_image', autospec=True) @mock.patch('ironic_python_agent.extensions.standby._verify_image',
autospec=True)
@mock.patch(OPEN_FUNCTION_NAME, autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
@mock.patch('requests.get', autospec=True) @mock.patch('requests.get', autospec=True)
def test_download_image_verify_fails(self, requests_mock, open_mock, def test_download_image_verify_fails(self, requests_mock, open_mock,
@ -224,8 +225,10 @@ class TestStandbyExtension(test_base.BaseTestCase):
self.assertEqual(md5_mock.call_count, 1) self.assertEqual(md5_mock.call_count, 1)
@mock.patch('ironic_python_agent.hardware.get_manager', autospec=True) @mock.patch('ironic_python_agent.hardware.get_manager', autospec=True)
@mock.patch('ironic_python_agent.standby._write_image', autospec=True) @mock.patch('ironic_python_agent.extensions.standby._write_image',
@mock.patch('ironic_python_agent.standby._download_image', autospec=True) autospec=True)
@mock.patch('ironic_python_agent.extensions.standby._download_image',
autospec=True)
def test_cache_image(self, download_mock, write_mock, hardware_mock): def test_cache_image(self, download_mock, write_mock, hardware_mock):
image_info = self._build_fake_image_info() image_info = self._build_fake_image_info()
download_mock.return_value = None download_mock.return_value = None
@ -242,14 +245,18 @@ class TestStandbyExtension(test_base.BaseTestCase):
self.assertEqual('SUCCEEDED', async_result.command_status) self.assertEqual('SUCCEEDED', async_result.command_status)
self.assertEqual(None, async_result.command_result) self.assertEqual(None, async_result.command_result)
@mock.patch('ironic_python_agent.standby._copy_configdrive_to_disk', @mock.patch(('ironic_python_agent.extensions.standby.'
'_copy_configdrive_to_disk'),
autospec=True) autospec=True)
@mock.patch('ironic_python_agent.standby.configdrive.write_configdrive', @mock.patch(('ironic_python_agent.extensions.standby.configdrive.'
'write_configdrive'),
autospec=True) autospec=True)
@mock.patch('ironic_python_agent.hardware.get_manager', autospec=True) @mock.patch('ironic_python_agent.hardware.get_manager', autospec=True)
@mock.patch('ironic_python_agent.standby._write_image', autospec=True) @mock.patch('ironic_python_agent.extensions.standby._write_image',
@mock.patch('ironic_python_agent.standby._download_image', autospec=True) autospec=True)
@mock.patch('ironic_python_agent.standby._configdrive_location', @mock.patch('ironic_python_agent.extensions.standby._download_image',
autospec=True)
@mock.patch('ironic_python_agent.extensions.standby._configdrive_location',
autospec=True) autospec=True)
def test_prepare_image(self, def test_prepare_image(self,
location_mock, location_mock,

View File

@ -19,9 +19,9 @@ console_scripts =
ironic-python-agent = ironic_python_agent.cmd.agent:run ironic-python-agent = ironic_python_agent.cmd.agent:run
ironic_python_agent.extensions = ironic_python_agent.extensions =
standby = ironic_python_agent.standby:StandbyExtension standby = ironic_python_agent.extensions.standby:StandbyExtension
decom = ironic_python_agent.decom:DecomExtension decom = ironic_python_agent.extensions.decom:DecomExtension
flow = ironic_python_agent.flow:FlowExtension flow = ironic_python_agent.extensions.flow:FlowExtension
ironic_python_agent.hardware_managers = ironic_python_agent.hardware_managers =
generic = ironic_python_agent.hardware:GenericHardwareManager generic = ironic_python_agent.hardware:GenericHardwareManager