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:
parent
f57301fae9
commit
246775da42
4
Makefile
4
Makefile
|
@ -45,7 +45,7 @@ external_dep: requirements-host.txt
|
||||||
|
|
||||||
# Run unit and Postgres integration tests in coverage mode
|
# Run unit and Postgres integration tests in coverage mode
|
||||||
.PHONY: coverage_test
|
.PHONY: coverage_test
|
||||||
coverage_test: build_drydock external_dep
|
coverage_test: build_drydock
|
||||||
tox -re cover
|
tox -re cover
|
||||||
|
|
||||||
# Run just unit tests
|
# Run just unit tests
|
||||||
|
@ -101,7 +101,7 @@ helm-install:
|
||||||
# Make targets intended for use by the primary targets above.
|
# Make targets intended for use by the primary targets above.
|
||||||
|
|
||||||
.PHONY: build_drydock
|
.PHONY: build_drydock
|
||||||
build_drydock:
|
build_drydock: external_dep
|
||||||
ifeq ($(USE_PROXY), true)
|
ifeq ($(USE_PROXY), true)
|
||||||
docker build --network host -t $(IMAGE) --label $(LABEL) -f images/drydock/Dockerfile \
|
docker build --network host -t $(IMAGE) --label $(LABEL) -f images/drydock/Dockerfile \
|
||||||
--build-arg http_proxy=$(PROXY) \
|
--build-arg http_proxy=$(PROXY) \
|
||||||
|
|
|
@ -30,3 +30,22 @@ class NodeList(CliAction): # pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
return self.api_client.get_nodes()
|
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)
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
"""
|
"""
|
||||||
import click
|
import click
|
||||||
import json
|
import json
|
||||||
|
import yaml
|
||||||
|
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
from drydock_provisioner.cli.node.actions import NodeList
|
from drydock_provisioner.cli.node.actions import NodeList
|
||||||
|
from drydock_provisioner.cli.node.actions import NodeBuildData
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
@ -54,3 +56,27 @@ def node_list(ctx, output='table'):
|
||||||
click.echo(pt)
|
click.echo(pt)
|
||||||
elif output == 'json':
|
elif output == 'json':
|
||||||
click.echo(json.dumps(nodelist))
|
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))
|
||||||
|
|
|
@ -141,3 +141,18 @@ class TaskShow(CliAction): # pylint: disable=too-few-public-methods
|
||||||
task = self.api_client.get_task(task_id=task_id)
|
task = self.api_client.get_task(task_id=task_id)
|
||||||
if task.status in [TaskStatus.Complete, TaskStatus.Terminated]:
|
if task.status in [TaskStatus.Complete, TaskStatus.Terminated]:
|
||||||
return task
|
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)
|
||||||
|
|
|
@ -14,10 +14,12 @@
|
||||||
"""Contains commands related to tasks against designs."""
|
"""Contains commands related to tasks against designs."""
|
||||||
import click
|
import click
|
||||||
import json
|
import json
|
||||||
|
import yaml
|
||||||
|
|
||||||
from drydock_provisioner.cli.task.actions import TaskList
|
from drydock_provisioner.cli.task.actions import TaskList
|
||||||
from drydock_provisioner.cli.task.actions import TaskShow
|
from drydock_provisioner.cli.task.actions import TaskShow
|
||||||
from drydock_provisioner.cli.task.actions import TaskCreate
|
from drydock_provisioner.cli.task.actions import TaskCreate
|
||||||
|
from drydock_provisioner.cli.task.actions import TaskBuildData
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
@ -105,3 +107,26 @@ def task_show(ctx, task_id=None, block=False):
|
||||||
|
|
||||||
click.echo(
|
click.echo(
|
||||||
json.dumps(TaskShow(ctx.obj['CLIENT'], task_id=task_id).invoke()))
|
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))
|
||||||
|
|
|
@ -711,10 +711,9 @@ class ConfigureNodeProvisioner(BaseMaasAction):
|
||||||
self.task.failure()
|
self.task.failure()
|
||||||
if repo_list.remove_unlisted:
|
if repo_list.remove_unlisted:
|
||||||
defined_repos = [x.get_id() for x in repo_list]
|
defined_repos = [x.get_id() for x in repo_list]
|
||||||
to_delete = [r
|
to_delete = [
|
||||||
for r
|
r for r in current_repos if r.name not in defined_repos
|
||||||
in current_repos
|
]
|
||||||
if r.name not in defined_repos]
|
|
||||||
for r in to_delete:
|
for r in to_delete:
|
||||||
if r.name not in self.DEFAULT_REPOS:
|
if r.name not in self.DEFAULT_REPOS:
|
||||||
r.delete()
|
r.delete()
|
||||||
|
@ -745,11 +744,13 @@ class ConfigureNodeProvisioner(BaseMaasAction):
|
||||||
model_fields['distributions'] = ','.join(repo_obj.distributions)
|
model_fields['distributions'] = ','.join(repo_obj.distributions)
|
||||||
if repo_obj.components:
|
if repo_obj.components:
|
||||||
if repo_obj.get_id() in ConfigureNodeProvisioner.DEFAULT_REPOS:
|
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:
|
else:
|
||||||
model_fields['components'] = ','.join(repo_obj.components)
|
model_fields['components'] = ','.join(repo_obj.components)
|
||||||
if repo_obj.get_disabled_subrepos():
|
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:
|
if repo_obj.arches:
|
||||||
model_fields['arches'] = ','.join(repo_obj.arches)
|
model_fields['arches'] = ','.join(repo_obj.arches)
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,31 @@ class DrydockClient(object):
|
||||||
self.session = session
|
self.session = session
|
||||||
self.logger = logging.getLogger(__name__)
|
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):
|
def get_nodes(self):
|
||||||
"""Get list of nodes in MaaS and their status."""
|
"""Get list of nodes in MaaS and their status."""
|
||||||
endpoint = 'v1.0/nodes'
|
endpoint = 'v1.0/nodes'
|
||||||
|
|
|
@ -132,6 +132,7 @@ class Repository(base.DrydockObject):
|
||||||
std = self.STANDARD_SUBREPOS.get(self.repo_type, ())
|
std = self.STANDARD_SUBREPOS.get(self.repo_type, ())
|
||||||
return std - enabled
|
return std - enabled
|
||||||
|
|
||||||
|
|
||||||
@base.DrydockObjectRegistry.register
|
@base.DrydockObjectRegistry.register
|
||||||
class RepositoryList(base.DrydockObjectListBase, base.DrydockObject):
|
class RepositoryList(base.DrydockObjectListBase, base.DrydockObject):
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,18 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# 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 yaml
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
import drydock_provisioner.drydock_client.session as dc_session
|
import drydock_provisioner.drydock_client.session as dc_session
|
||||||
import drydock_provisioner.drydock_client.client as dc_client
|
import drydock_provisioner.drydock_client.client as dc_client
|
||||||
|
|
||||||
from drydock_provisioner.cli.task.actions import TaskCreate
|
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():
|
def test_taskcli_blank_nodefilter():
|
||||||
"""If no filter values are specified, node filter should be None."""
|
"""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")
|
dd_client, "http://foo.bar", action_name="deploy_nodes")
|
||||||
|
|
||||||
assert action.node_filter is None
|
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
|
||||||
|
|
Loading…
Reference in New Issue