Add build data for MAAS logs
- Collect logs from MAAS when failures happen during deployment - Save the logs to build data so it is available via API - Add postgres integration test Change-Id: Ied2d8539fe02a75f1f175a421b897b4f8ce07c8d
This commit is contained in:
parent
eb996c27f4
commit
f57301fae9
7
Makefile
7
Makefile
@ -51,7 +51,12 @@ coverage_test: build_drydock external_dep
|
|||||||
# Run just unit tests
|
# Run just unit tests
|
||||||
.PHONY: unit_tests
|
.PHONY: unit_tests
|
||||||
unit_tests: external_dep
|
unit_tests: external_dep
|
||||||
tox -re py35
|
tox -re py35 $(TESTS)
|
||||||
|
|
||||||
|
# Run just DB integration tests
|
||||||
|
.PHONY: db_integration_tests
|
||||||
|
db_integration_tests: external_dep
|
||||||
|
tox -re integration $(TESTS)
|
||||||
|
|
||||||
# Freeze full set of Python requirements
|
# Freeze full set of Python requirements
|
||||||
.PHONY: req_freeze
|
.PHONY: req_freeze
|
||||||
|
@ -20,6 +20,7 @@ from .designs import DesignsPartsKindsResource
|
|||||||
from .designs import DesignsPartResource
|
from .designs import DesignsPartResource
|
||||||
from .tasks import TasksResource
|
from .tasks import TasksResource
|
||||||
from .tasks import TaskResource
|
from .tasks import TaskResource
|
||||||
|
from .tasks import TaskBuilddataResource
|
||||||
from .nodes import NodesResource
|
from .nodes import NodesResource
|
||||||
from .nodes import NodeBuildDataResource
|
from .nodes import NodeBuildDataResource
|
||||||
from .nodes import NodeFilterResource
|
from .nodes import NodeFilterResource
|
||||||
@ -67,6 +68,8 @@ def start_api(state_manager=None, ingester=None, orchestrator=None):
|
|||||||
TasksResource(state_manager=state_manager,
|
TasksResource(state_manager=state_manager,
|
||||||
orchestrator=orchestrator)),
|
orchestrator=orchestrator)),
|
||||||
('/tasks/{task_id}', TaskResource(state_manager=state_manager)),
|
('/tasks/{task_id}', TaskResource(state_manager=state_manager)),
|
||||||
|
('/tasks/{task_id}/builddata',
|
||||||
|
TaskBuilddataResource(state_manager=state_manager)),
|
||||||
|
|
||||||
# API for managing site design data
|
# API for managing site design data
|
||||||
('/designs', DesignsResource(state_manager=state_manager)),
|
('/designs', DesignsResource(state_manager=state_manager)),
|
||||||
|
@ -390,3 +390,20 @@ class TaskResource(StatefulResource):
|
|||||||
# Finished this layer, incrementing for the next while loop.
|
# Finished this layer, incrementing for the next while loop.
|
||||||
current_layer = current_layer + 1
|
current_layer = current_layer + 1
|
||||||
return resp_data, errors
|
return resp_data, errors
|
||||||
|
|
||||||
|
|
||||||
|
class TaskBuilddataResource(StatefulResource):
|
||||||
|
"""Handler resource for /tasks/<id>/builddata singleton endpoint."""
|
||||||
|
|
||||||
|
@policy.ApiEnforcer('physical_provisioner:read_build_data')
|
||||||
|
def on_get(self, req, resp, task_id):
|
||||||
|
try:
|
||||||
|
bd_list = self.state_manager.get_build_data(task_id=task_id)
|
||||||
|
if not bd_list:
|
||||||
|
resp.status = falcon.HTTP_404
|
||||||
|
return
|
||||||
|
resp.body = json.dumps(bd_list)
|
||||||
|
except Exception as e:
|
||||||
|
resp.body = "Unexpected error."
|
||||||
|
resp.status = falcon.HTTP_500
|
||||||
|
resp.status = falcon.HTTP_200
|
||||||
|
@ -16,11 +16,13 @@ from drydock_provisioner.error import ApiError
|
|||||||
from drydock_provisioner.drydock_client.session import KeystoneClient
|
from drydock_provisioner.drydock_client.session import KeystoneClient
|
||||||
from drydock_provisioner.util import KeystoneUtils
|
from drydock_provisioner.util import KeystoneUtils
|
||||||
|
|
||||||
|
|
||||||
def get_internal_api_href(ver):
|
def get_internal_api_href(ver):
|
||||||
"""Get the internal API href for Drydock API version ``ver``."""
|
"""Get the internal API href for Drydock API version ``ver``."""
|
||||||
|
|
||||||
# TODO(sh8121att) Support versioned service registration
|
# TODO(sh8121att) Support versioned service registration
|
||||||
supported_versions = ['v1.0']
|
supported_versions = ['v1.0']
|
||||||
|
|
||||||
if ver in supported_versions:
|
if ver in supported_versions:
|
||||||
ks_sess = KeystoneUtils.get_session()
|
ks_sess = KeystoneUtils.get_session()
|
||||||
url = KeystoneClient.get_endpoint(
|
url = KeystoneClient.get_endpoint(
|
||||||
|
@ -27,6 +27,7 @@ import drydock_provisioner.objects.fields as hd_fields
|
|||||||
import drydock_provisioner.objects.hostprofile as hostprofile
|
import drydock_provisioner.objects.hostprofile as hostprofile
|
||||||
import drydock_provisioner.objects as objects
|
import drydock_provisioner.objects as objects
|
||||||
|
|
||||||
|
from drydock_provisioner.control.util import get_internal_api_href
|
||||||
from drydock_provisioner.orchestrator.actions.orchestrator import BaseAction
|
from drydock_provisioner.orchestrator.actions.orchestrator import BaseAction
|
||||||
|
|
||||||
import drydock_provisioner.drivers.node.maasdriver.models.fabric as maas_fabric
|
import drydock_provisioner.drivers.node.maasdriver.models.fabric as maas_fabric
|
||||||
@ -51,6 +52,22 @@ class BaseMaasAction(BaseAction):
|
|||||||
self.logger = logging.getLogger(
|
self.logger = logging.getLogger(
|
||||||
config.config_mgr.conf.logging.nodedriver_logger_name)
|
config.config_mgr.conf.logging.nodedriver_logger_name)
|
||||||
|
|
||||||
|
def _add_detail_logs(self, node, machine, data_gen, result_type='all'):
|
||||||
|
result_details = machine.get_task_results(result_type=result_type)
|
||||||
|
for r in result_details:
|
||||||
|
bd = objects.BuildData(
|
||||||
|
node_name=node.name,
|
||||||
|
task_id=self.task.task_id,
|
||||||
|
collected_date=r.updated,
|
||||||
|
generator=data_gen,
|
||||||
|
data_format='text/plain',
|
||||||
|
data_element=r.get_decoded_data())
|
||||||
|
self.state_manager.post_build_data(bd)
|
||||||
|
log_href = "%s/tasks/%s/builddata" % (
|
||||||
|
get_internal_api_href("v1.0"), str(self.task.task_id))
|
||||||
|
self.task.result.add_link('detail_logs', log_href)
|
||||||
|
self.task.save()
|
||||||
|
|
||||||
|
|
||||||
class ValidateNodeServices(BaseMaasAction):
|
class ValidateNodeServices(BaseMaasAction):
|
||||||
"""Action to validate MaaS is available and ready for use."""
|
"""Action to validate MaaS is available and ready for use."""
|
||||||
@ -983,6 +1000,25 @@ class ConfigureHardware(BaseMaasAction):
|
|||||||
ctx_type='node')
|
ctx_type='node')
|
||||||
self.task.success(focus=n.get_id())
|
self.task.success(focus=n.get_id())
|
||||||
self.collect_build_data(machine)
|
self.collect_build_data(machine)
|
||||||
|
else:
|
||||||
|
msg = "Node %s failed commissioning." % (n.name)
|
||||||
|
self.logger.info(msg)
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg=msg,
|
||||||
|
error=True,
|
||||||
|
ctx=n.name,
|
||||||
|
ctx_type='node')
|
||||||
|
self.task.failure(focus=n.get_id())
|
||||||
|
self._add_detail_logs(
|
||||||
|
n,
|
||||||
|
machine,
|
||||||
|
'maas_commission_log',
|
||||||
|
result_type='commissioning')
|
||||||
|
self._add_detail_logs(
|
||||||
|
n,
|
||||||
|
machine,
|
||||||
|
'maas_testing_log',
|
||||||
|
result_type='testing')
|
||||||
elif machine.status_name in ['Commissioning', 'Testing']:
|
elif machine.status_name in ['Commissioning', 'Testing']:
|
||||||
msg = "Located node %s in MaaS, node already being commissioned. Skipping..." % (
|
msg = "Located node %s in MaaS, node already being commissioned. Skipping..." % (
|
||||||
n.name)
|
n.name)
|
||||||
@ -2123,6 +2159,12 @@ class DeployNode(BaseMaasAction):
|
|||||||
self.task.add_status_msg(
|
self.task.add_status_msg(
|
||||||
msg=msg, error=False, ctx=n.name, ctx_type='node')
|
msg=msg, error=False, ctx=n.name, ctx_type='node')
|
||||||
self.task.success(focus=n.get_id())
|
self.task.success(focus=n.get_id())
|
||||||
|
elif machine.status_name.startswith('Failed'):
|
||||||
|
msg = "Node %s deployment failed" % (n.name)
|
||||||
|
self.logger.info(msg)
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg=msg, error=True, ctx=n.name, ctx_type='node')
|
||||||
|
self.task.failure(focus=n.get_id())
|
||||||
else:
|
else:
|
||||||
msg = "Node %s deployment timed out" % (n.name)
|
msg = "Node %s deployment timed out" % (n.name)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
@ -2130,6 +2172,9 @@ class DeployNode(BaseMaasAction):
|
|||||||
msg=msg, error=True, ctx=n.name, ctx_type='node')
|
msg=msg, error=True, ctx=n.name, ctx_type='node')
|
||||||
self.task.failure(focus=n.get_id())
|
self.task.failure(focus=n.get_id())
|
||||||
|
|
||||||
|
self._add_detail_logs(
|
||||||
|
n, machine, 'maas_deploy_log', result_type='deploy')
|
||||||
|
|
||||||
self.task.set_status(hd_fields.TaskStatus.Complete)
|
self.task.set_status(hd_fields.TaskStatus.Complete)
|
||||||
self.task.save()
|
self.task.save()
|
||||||
|
|
||||||
|
@ -304,7 +304,9 @@ class Machine(model_base.ResourceBase):
|
|||||||
``all``, ``commissioning``, ``testing``, ``deploy``
|
``all``, ``commissioning``, ``testing``, ``deploy``
|
||||||
"""
|
"""
|
||||||
node_results = maas_nr.NodeResults(
|
node_results = maas_nr.NodeResults(
|
||||||
system_id_list=[self.resource_id], result_type=result_type)
|
self.api_client,
|
||||||
|
system_id_list=[self.resource_id],
|
||||||
|
result_type=result_type)
|
||||||
node_results.refresh()
|
node_results.refresh()
|
||||||
|
|
||||||
return node_results
|
return node_results
|
||||||
|
85
tests/integration/postgres/test_noderesult_links.py
Normal file
85
tests/integration/postgres/test_noderesult_links.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""Test build data collection and persistence."""
|
||||||
|
from drydock_provisioner.objects import fields as hd_fields
|
||||||
|
from drydock_provisioner.drivers.node.maasdriver.actions.node import BaseMaasAction
|
||||||
|
from drydock_provisioner.drivers.node.maasdriver.models.machine import Machine
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeResultLinks(object):
|
||||||
|
def test_create_detail_log_links(self, setup, blank_state, mocker,
|
||||||
|
input_files, deckhand_orchestrator):
|
||||||
|
"""Test that the detail log collection from MaaS works."""
|
||||||
|
|
||||||
|
class MockedResponse():
|
||||||
|
|
||||||
|
status_code = 200
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
resp_content = [{
|
||||||
|
"id":
|
||||||
|
3,
|
||||||
|
"data":
|
||||||
|
"SGVsbG8gV29ybGQh",
|
||||||
|
"result_type":
|
||||||
|
0,
|
||||||
|
"script_result":
|
||||||
|
0,
|
||||||
|
"resource_uri":
|
||||||
|
"/MAAS/api/2.0/commissioning-scripts/",
|
||||||
|
"updated":
|
||||||
|
"2018-07-06T14:32:20.129",
|
||||||
|
"node": {
|
||||||
|
"system_id": "r7mqnw"
|
||||||
|
},
|
||||||
|
"created":
|
||||||
|
"2018-07-06T14:37:12.632",
|
||||||
|
"name":
|
||||||
|
"hello_world"
|
||||||
|
}]
|
||||||
|
|
||||||
|
return resp_content
|
||||||
|
|
||||||
|
input_file = input_files.join("deckhand_fullsite.yaml")
|
||||||
|
design_ref = "file://%s" % str(input_file)
|
||||||
|
|
||||||
|
task = deckhand_orchestrator.create_task(
|
||||||
|
action=hd_fields.OrchestratorAction.Noop, design_ref=design_ref)
|
||||||
|
task.set_status(hd_fields.TaskStatus.Running)
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
api_client = mocker.MagicMock()
|
||||||
|
api_client.get.return_value = MockedResponse()
|
||||||
|
|
||||||
|
machine = Machine(api_client)
|
||||||
|
machine.resource_id = 'r7mqnw'
|
||||||
|
node = mocker.MagicMock()
|
||||||
|
node.configure_mock(name='n1')
|
||||||
|
|
||||||
|
action = BaseMaasAction(task, deckhand_orchestrator, blank_state)
|
||||||
|
|
||||||
|
with mocker.patch(
|
||||||
|
'drydock_provisioner.drivers.node.maasdriver.actions.node.get_internal_api_href',
|
||||||
|
mocker.MagicMock(return_value='http://drydock/api/v1.0')):
|
||||||
|
action._add_detail_logs(node, machine, 'hello_world')
|
||||||
|
|
||||||
|
bd = blank_state.get_build_data(task_id=task.task_id)
|
||||||
|
assert len(bd) == 1
|
||||||
|
|
||||||
|
links_list = task.result.get_links()
|
||||||
|
|
||||||
|
assert len(links_list) > 0
|
||||||
|
|
||||||
|
for l in links_list:
|
||||||
|
assert str(task.task_id) in l
|
@ -18,6 +18,7 @@ from drydock_provisioner.drivers.node.maasdriver.models.node_results import Node
|
|||||||
class TestMaasNodeResults():
|
class TestMaasNodeResults():
|
||||||
def test_get_noderesults(self, mocker):
|
def test_get_noderesults(self, mocker):
|
||||||
'''Test noderesults refresh call to load a list of NodeResults.'''
|
'''Test noderesults refresh call to load a list of NodeResults.'''
|
||||||
|
|
||||||
# A object to return that looks like a requests response
|
# A object to return that looks like a requests response
|
||||||
# object wrapping a MAAS API response
|
# object wrapping a MAAS API response
|
||||||
class MockedResponse():
|
class MockedResponse():
|
||||||
@ -26,17 +27,25 @@ class TestMaasNodeResults():
|
|||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
resp_content = [{
|
resp_content = [{
|
||||||
"id": 3,
|
"id":
|
||||||
"data": "SGVsbG8gV29ybGQh",
|
3,
|
||||||
"result_type": 0,
|
"data":
|
||||||
"script_result": 0,
|
"SGVsbG8gV29ybGQh",
|
||||||
"resource_uri": "/MAAS/api/2.0/commissioning-scripts/",
|
"result_type":
|
||||||
"updated": "2018-07-06T14:32:20.129",
|
0,
|
||||||
|
"script_result":
|
||||||
|
0,
|
||||||
|
"resource_uri":
|
||||||
|
"/MAAS/api/2.0/commissioning-scripts/",
|
||||||
|
"updated":
|
||||||
|
"2018-07-06T14:32:20.129",
|
||||||
"node": {
|
"node": {
|
||||||
"system_id": "r7mqnw"
|
"system_id": "r7mqnw"
|
||||||
},
|
},
|
||||||
"created": "2018-07-06T14:37:12.632",
|
"created":
|
||||||
"name": "hello_world"
|
"2018-07-06T14:37:12.632",
|
||||||
|
"name":
|
||||||
|
"hello_world"
|
||||||
}]
|
}]
|
||||||
|
|
||||||
return resp_content
|
return resp_content
|
||||||
|
Loading…
Reference in New Issue
Block a user