Add build data access to Drydock client

- Add CLI actions to output the build data for a node or
  a task
- Add API methods to access the Drydock API to retrieve node
  or task build data

Change-Id: I0ee01bd4b165b93c2bc0e3050554514ba40f152a
This commit is contained in:
Scott Hussey 2018-07-20 08:35:52 -05:00
parent f57301fae9
commit 246775da42
9 changed files with 179 additions and 8 deletions

View File

@ -45,7 +45,7 @@ external_dep: requirements-host.txt
# Run unit and Postgres integration tests in coverage mode
.PHONY: coverage_test
coverage_test: build_drydock external_dep
coverage_test: build_drydock
tox -re cover
# Run just unit tests
@ -101,7 +101,7 @@ helm-install:
# Make targets intended for use by the primary targets above.
.PHONY: build_drydock
build_drydock:
build_drydock: external_dep
ifeq ($(USE_PROXY), true)
docker build --network host -t $(IMAGE) --label $(LABEL) -f images/drydock/Dockerfile \
--build-arg http_proxy=$(PROXY) \

View File

@ -30,3 +30,22 @@ class NodeList(CliAction): # pylint: disable=too-few-public-methods
def invoke(self):
return self.api_client.get_nodes()
class NodeBuildData(CliAction):
""" Action to print node build data."""
def __init__(self, api_client, nodename, latest):
"""
:param DrydockClient api_client: the api client used for invocation.
:param str nodename: The name of the node to retrieve data for.
:param bool latest: If only the latest build data should be retrieved.
"""
super().__init__(api_client)
self.nodename = nodename
self.latest = latest
self.logger.debug('NodeBuildData action initialized')
def invoke(self):
return self.api_client.get_node_build_data(
self.nodename, latest=self.latest)

View File

@ -16,10 +16,12 @@
"""
import click
import json
import yaml
from prettytable import PrettyTable
from drydock_provisioner.cli.node.actions import NodeList
from drydock_provisioner.cli.node.actions import NodeBuildData
@click.group()
@ -54,3 +56,27 @@ def node_list(ctx, output='table'):
click.echo(pt)
elif output == 'json':
click.echo(json.dumps(nodelist))
@node.command(name='builddata')
@click.option(
'--latest/--no-latest',
help='Retrieve only the latest data items.',
default=True)
@click.option(
'--output', '-o', help='Output format: yaml|json', default='yaml')
@click.argument('nodename')
@click.pass_context
def node_builddata(ctx, nodename, latest=True, output='yaml'):
"""List build data for ``nodename``."""
node_bd = NodeBuildData(ctx.obj['CLIENT'], nodename, latest).invoke()
if output == 'json':
click.echo(json.dumps(node_bd))
else:
if output != 'yaml':
click.echo(
"Invalid output format {}, default to YAML.".format(output))
click.echo(
yaml.safe_dump(
node_bd, allow_unicode=True, default_flow_style=False))

View File

@ -141,3 +141,18 @@ class TaskShow(CliAction): # pylint: disable=too-few-public-methods
task = self.api_client.get_task(task_id=task_id)
if task.status in [TaskStatus.Complete, TaskStatus.Terminated]:
return task
class TaskBuildData(CliAction):
"""Action to retrieve task build data."""
def __init__(self, api_client, task_id):
"""
:param DrydockClient api_client: the api client instance used for invocation.
:param str task_id: A UUID-like task_id
"""
super().__init__(api_client)
self.task_id = task_id
def invoke(self):
return self.api_client.get_task_build_data(self.task_id)

View File

@ -14,10 +14,12 @@
"""Contains commands related to tasks against designs."""
import click
import json
import yaml
from drydock_provisioner.cli.task.actions import TaskList
from drydock_provisioner.cli.task.actions import TaskShow
from drydock_provisioner.cli.task.actions import TaskCreate
from drydock_provisioner.cli.task.actions import TaskBuildData
@click.group()
@ -105,3 +107,26 @@ def task_show(ctx, task_id=None, block=False):
click.echo(
json.dumps(TaskShow(ctx.obj['CLIENT'], task_id=task_id).invoke()))
@task.command(name='builddata')
@click.option('--task-id', '-t', help='The required task id')
@click.option(
'--output', '-o', help='The output format (yaml|json)', default='yaml')
@click.pass_context
def task_builddata(ctx, task_id=None, output='yaml'):
"""Show builddata assoicated with ``task_id``."""
if not task_id:
ctx.fail('The task id must be specified by --task-id')
task_bd = TaskBuildData(ctx.obj['CLIENT'], task_id=task_id).invoke()
if output == 'json':
click.echo(json.dumps(task_bd))
else:
if output != 'yaml':
click.echo(
'Invalid output format {}, defaulting to YAML.'.format(output))
click.echo(
yaml.safe_dump(
task_bd, allow_unicode=True, default_flow_style=False))

View File

@ -711,10 +711,9 @@ class ConfigureNodeProvisioner(BaseMaasAction):
self.task.failure()
if repo_list.remove_unlisted:
defined_repos = [x.get_id() for x in repo_list]
to_delete = [r
for r
in current_repos
if r.name not in defined_repos]
to_delete = [
r for r in current_repos if r.name not in defined_repos
]
for r in to_delete:
if r.name not in self.DEFAULT_REPOS:
r.delete()
@ -745,11 +744,13 @@ class ConfigureNodeProvisioner(BaseMaasAction):
model_fields['distributions'] = ','.join(repo_obj.distributions)
if repo_obj.components:
if repo_obj.get_id() in ConfigureNodeProvisioner.DEFAULT_REPOS:
model_fields['disabled_components'] = ','.join(repo_obj.get_disabled_components())
model_fields['disabled_components'] = ','.join(
repo_obj.get_disabled_components())
else:
model_fields['components'] = ','.join(repo_obj.components)
if repo_obj.get_disabled_subrepos():
model_fields['disabled_pockets'] = ','.join(repo_obj.get_disabled_subrepos())
model_fields['disabled_pockets'] = ','.join(
repo_obj.get_disabled_subrepos())
if repo_obj.arches:
model_fields['arches'] = ','.join(repo_obj.arches)

View File

@ -29,6 +29,31 @@ class DrydockClient(object):
self.session = session
self.logger = logging.getLogger(__name__)
def get_task_build_data(self, task_id):
"""Get the build data associated with ``task_id``.
:param str task_id: A UUID-formatted task ID
:return: A list of dictionaries resembling objects.builddata.BuildData
"""
endpoint = 'v1.0/tasks/{}/builddata'.format(task_id)
resp = self.session.get(endpoint)
self._check_response(resp)
return resp.json()
def get_node_build_data(self, nodename, latest=True):
"""Get the build data associated with ``nodename``.
:param str nodename: Name of the node
:param bool latest: Whether to request only the latest version of each data item
:return: A list of dictionaries resembling objects.builddata.BuildData
"""
endpoint = 'v1.0/nodes/{}/builddata?latest={}'.format(nodename, latest)
resp = self.session.get(endpoint)
self._check_response(resp)
return resp.json()
def get_nodes(self):
"""Get list of nodes in MaaS and their status."""
endpoint = 'v1.0/nodes'

View File

@ -132,6 +132,7 @@ class Repository(base.DrydockObject):
std = self.STANDARD_SUBREPOS.get(self.repo_type, ())
return std - enabled
@base.DrydockObjectRegistry.register
class RepositoryList(base.DrydockObjectListBase, base.DrydockObject):

View File

@ -11,11 +11,18 @@
# 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 yaml
import pytest
from click.testing import CliRunner
import drydock_provisioner.drydock_client.session as dc_session
import drydock_provisioner.drydock_client.client as dc_client
from drydock_provisioner.cli.task.actions import TaskCreate
from drydock_provisioner.cli.task.actions import TaskBuildData
import drydock_provisioner.cli.commands as cli
def test_taskcli_blank_nodefilter():
"""If no filter values are specified, node filter should be None."""
@ -29,3 +36,55 @@ def test_taskcli_blank_nodefilter():
dd_client, "http://foo.bar", action_name="deploy_nodes")
assert action.node_filter is None
def test_taskcli_builddata_action(mocker):
"""Test the CLI task get build data routine."""
task_id = "aaaa-bbbb-cccc-dddd"
build_data = [{
"node_name": "foo",
"task_id": task_id,
"collected_data": "1/1/2000",
"generator": "test",
"data_format": "text/plain",
"data_element": "Hello World!",
}]
api_client = mocker.MagicMock()
api_client.get_task_build_data.return_value = build_data
bd_action = TaskBuildData(api_client, task_id)
assert bd_action.invoke() == build_data
api_client.get_task_build_data.assert_called_with(task_id)
@pytest.mark.skip(reason='Working on mocking needed for click.testing')
def test_taskcli_builddata_command(mocker):
"""Test the CLI task get build data command."""
task_id = "aaaa-bbbb-cccc-dddd"
build_data = [{
"node_name": "foo",
"task_id": task_id,
"collected_data": "1/1/2000",
"generator": "test",
"data_format": "text/plain",
"data_element": "Hello World!",
}]
api_client = mocker.MagicMock()
api_client.get_task_build_data.return_value = build_data
mocker.patch('drydock_provisioner.cli.commands.DrydockClient', new=api_client)
mocker.patch('drydock_provisioner.cli.commands.KeystoneClient')
runner = CliRunner()
result = runner.invoke(cli.drydock, ['-u',
'http://foo',
'task',
'builddata',
'-t',
task_id])
print(result.exc_info)
api_client.get_task_build_data.assert_called_with(task_id)
assert yaml.safe_dump(build_data, allow_unicode=True, default_flow_style=False) in result.output