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:
parent
2960f71c64
commit
5ebd2e9797
@ -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
|
||||||
|
@ -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
|
|
0
ironic_python_agent/extensions/__init__.py
Normal file
0
ironic_python_agent/extensions/__init__.py
Normal 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
|
@ -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):
|
@ -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():
|
@ -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')
|
@ -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')
|
||||||
|
@ -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'
|
||||||
|
0
ironic_python_agent/tests/extensions/__init__.py
Normal file
0
ironic_python_agent/tests/extensions/__init__.py
Normal 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):
|
@ -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):
|
@ -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'])
|
||||||
|
|
@ -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,
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user