Added the audit scope field in dashboard

In this changeset, I added the scope field in the dashboard using a
textarea that verifies that the given audit scope is a valid YAML/JSON
data structure.

I also removed obsolete object fields: deadline in Audit object and
extra in Audit Template object.

Partially Implements: blueprint define-the-audit-scope

Change-Id: Icb30557a6a4b3a698f3a6f9ccec57223b60b0b53
This commit is contained in:
Vincent Francoise 2016-11-04 17:41:25 +01:00 committed by David TARDIVEL
parent b7a2b73dfd
commit 1a900cfbf6
14 changed files with 96 additions and 45 deletions

View File

@ -10,6 +10,7 @@ django-openstack-auth>=2.4.0 # Apache-2.0
httplib2>=0.7.5 # MIT
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
pytz>=2013.6 # MIT
PyYAML>=3.10.0 # MIT
# Watcher-specific requirements
python-watcherclient>=0.23.0 # Apache-2.0

View File

@ -56,7 +56,7 @@ def insert_watcher_policy_file():
class Audit(base.APIDictWrapper):
_attrs = ('uuid', 'created_at', 'modified_at', 'deleted_at',
'deadline', 'state', 'audit_type', 'audit_template_uuid',
'state', 'audit_type', 'audit_template_uuid',
'audit_template_name', 'interval')
def __init__(self, apiresource, request=None):
@ -65,7 +65,7 @@ class Audit(base.APIDictWrapper):
@classmethod
def create(cls, request, audit_template_uuid,
audit_type, deadline, interval=None):
audit_type, interval=None):
"""Create an audit in Watcher
@ -78,9 +78,6 @@ class Audit(base.APIDictWrapper):
:param audit_type: audit type
:type audit_type: string
:param deadline: audit deadline:
:type deadline: string
:param interval: Audit interval (default: None)
:type interval: int
@ -91,11 +88,10 @@ class Audit(base.APIDictWrapper):
if interval:
return watcherclient(request).audit.create(
audit_template_uuid=audit_template_uuid, audit_type=audit_type,
deadline=deadline, interval=interval)
interval=interval)
else:
return watcherclient(request).audit.create(
audit_template_uuid=audit_template_uuid, audit_type=audit_type,
deadline=deadline)
audit_template_uuid=audit_template_uuid, audit_type=audit_type)
@classmethod
def list(cls, request, **filters):
@ -147,9 +143,9 @@ class Audit(base.APIDictWrapper):
class AuditTemplate(base.APIDictWrapper):
_attrs = ('uuid', 'description', 'host_aggregate', 'name', 'extra',
'goal_uuid', 'goal_name', 'strategy_uuid', 'strategy_name',
'created_at', 'updated_at', 'deleted_at')
_attrs = ('uuid', 'description', 'scope', 'name', 'goal_uuid', 'goal_name',
'strategy_uuid', 'strategy_name', 'created_at', 'updated_at',
'deleted_at')
def __init__(self, apiresource, request=None):
super(AuditTemplate, self).__init__(apiresource)
@ -157,7 +153,7 @@ class AuditTemplate(base.APIDictWrapper):
@classmethod
def create(cls, request, name, goal, strategy,
description, host_aggregate):
description, scope):
"""Create an audit template in Watcher
:param request: request object
@ -176,9 +172,8 @@ class AuditTemplate(base.APIDictWrapper):
:param description: Descrition of the audit template
:type description: string
:param host_aggregate: Name or UUID of the host aggregate targeted
by this audit template
:type host_aggregate: string
:param scope: Audit scope
:type scope: list of list of dict
:param audit_template: audit template
:type audit_template: string
@ -191,7 +186,7 @@ class AuditTemplate(base.APIDictWrapper):
goal=goal,
strategy=strategy,
description=description,
host_aggregate=host_aggregate
scope=scope,
)
return audit_template

View File

@ -18,24 +18,45 @@ Forms for starting Watcher Audit Templates.
"""
import logging
from django.core import exceptions as core_exc
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
import yaml
from watcher_dashboard.api import watcher
LOG = logging.getLogger(__name__)
class YamlValidator(object):
message = _('Enter a valid YAML or JSON value.')
code = 'invalid'
def __init__(self, message=None):
if message:
self.message = message
def __call__(self, value):
try:
yaml.load(value)
except Exception:
raise core_exc.ValidationError(self.message, code=self.code)
class CreateForm(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Name"))
description = forms.CharField(max_length=255, label=_("Description"),
required=False)
goal = forms.ChoiceField(label=_('Goal'), required=True)
strategy = forms.ChoiceField(label=_('Strategy'), required=False)
strategy = forms.DynamicChoiceField(label=_('Strategy'), required=False)
scope = forms.CharField(
label=_('Scope'), required=False,
widget=forms.widgets.Textarea,
validators=[YamlValidator()])
failure_url = 'horizon:admin:audit_templates:index'
@ -103,7 +124,8 @@ class CreateForm(forms.SelfHandlingForm):
params['description'] = data['description']
params['goal'] = data['goal']
params['strategy'] = data['strategy'] or None
params['host_aggregate'] = None
params['scope'] = [] if not data['scope'] else yaml.load(
data['scope'])
audit_tpl = watcher.AuditTemplate.create(request, **params)
message = _('Audit Template was successfully created.')
messages.success(request, message)

View File

@ -69,7 +69,6 @@ class LaunchAudit(horizon.tables.BatchAction):
def action(self, request, obj_id):
params = {'audit_template_uuid': obj_id}
params['audit_type'] = 'ONESHOT'
params['deadline'] = None
watcher.Audit.create(request, **params)

View File

@ -86,7 +86,7 @@ class AuditTemplatesTest(test.BaseAdminViewTests):
'goal': at.goal_uuid,
'strategy': at.strategy_uuid,
'description': at.description,
'host_aggregate': at.host_aggregate,
'scope': at.scope,
}
api.watcher.Goal.list(
IsA(http.HttpRequest)).AndReturn(self.goal_list)

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
from django.core.urlresolvers import reverse_lazy
@ -94,6 +95,9 @@ class DetailView(horizon.tabs.TabbedTableView):
audit_template_uuid = self.kwargs['audit_template_uuid']
audit_template = watcher.AuditTemplate.get(
self.request, audit_template_uuid)
if audit_template.scope:
audit_template.scope = json.dumps(audit_template.scope)
except Exception as exc:
LOG.exception(exc)
msg = _('Unable to retrieve details for audit template "%s".') \

View File

@ -89,7 +89,6 @@ class CreateForm(forms.SelfHandlingForm):
params['interval'] = int(data['interval'].total_seconds())
else:
params['interval'] = None
params['deadline'] = None
audit = watcher.Audit.create(request, **params)
message = _('Audit was successfully created.')
messages.success(request, message)

View File

@ -25,8 +25,6 @@
<dd><a href="{{ next_action_url }}">{{ action.next_uuid|default:_("&mdash;") }}</a></dd>
<dt>{% trans "State" %}</dt>
<dd>{{ action.state|default:"&mdash;" }}</dd>
<dt>{% trans "Deadline" %}</dt>
<dd>{{ action.deadline|default:"&mdash;" }}</dd>
<dt>{% trans "Created At" %}</dt>
<dd>{{ action.created_at|default:"&mdash;" }}</dd>
<dt>{% trans "Update At" %}</dt>

View File

@ -6,5 +6,46 @@
<p>{% trans "Creates an audit template with specified parameters." %}</p>
<p>
<span>{% trans "Define the optimization goal to achieve, among those which are available." %}</span>
<span>{% trans "Optionally, you can select the strategy used to achieve your goal. If not set, a strategy will be automatically selected among those which can be used for your goal" %}</span></p>
<span>{% trans "Optionally, you can select the strategy used to achieve your goal. If not set, a strategy will be automatically selected among those which can be used for your goal" %}</span>
</p>
<h3>{% trans "Audit scope:" %}</h3>
<p>{% trans "Creates an audit template with specified parameters." %}</p>
<p>{% trans "Creates an audit template with specified parameters." %}</p>
<p class="scope-example-yaml">
{% trans "YAML example:" %}
---
- host_aggregates:
- id: 1
- id: 2
- id: 3
- availability_zones:
- name: AZ1
- name: AZ2
- exclude:
- instances:
- uuid: UUID1
- uuid: UUID2
- compute_nodes:
- name: compute1
</p>
<p>
{% trans "JSON example:" %}
[{'host_aggregates': [
{'id': 1},
{'id': 2},
{'id': 3}]},
{'availability_zones': [
{'name': 'AZ1'},
{'name': 'AZ2'}]},
{'exclude': [
{'instances': [
{'uuid': 'UUID1'},
{'uuid': 'UUID2'}
]},
{'compute_nodes': [
{'name': 'compute1'}
]}
]}]
</p>
{% endblock %}

View File

@ -19,6 +19,8 @@
<dd>{{ audit_template.goal_name|default:"&mdash;" }}</dd>
<dt>{% trans "Strategy" %}</dt>
<dd>{{ audit_template.strategy_name|default:"&mdash;" }}</dd>
<dt>{% trans "Audit Scope" %}</dt>
<dd><code>{{ audit_template.scope|linebreaks }}</code></dd>
<dt>{% trans "Created At" %}</dt>
<dd>{{ audit_template.created_at|default:"&mdash;" }}</dd>
<dt>{% trans "Update At" %}</dt>

View File

@ -22,8 +22,6 @@
{% url 'horizon:admin:audit_templates:detail' audit.audit_template_uuid as audit_template_url %}
<dt>{% trans "State" %}</dt>
<dd>{{ audit.state|default:"&mdash;" }}</dd>
<dt>{% trans "Deadline" %}</dt>
<dd>{{ audit.deadline|default:"&mdash;" }}</dd>
<dt>{% trans "Created At" %}</dt>
<dd>{{ audit.created_at|default:"&mdash;" }}</dd>
<dt>{% trans "Update At" %}</dt>

View File

@ -129,7 +129,7 @@ class WatcherAPITests(test.APITestCase):
goal = audit_template['goal_uuid']
strategy = audit_template['strategy_uuid']
description = audit_template['description']
host_aggregate = audit_template['host_aggregate']
scope = audit_template['scope']
watcherclient = self.stub_watcherclient()
watcherclient.audit_template = self.mox.CreateMockAnything()
@ -138,12 +138,12 @@ class WatcherAPITests(test.APITestCase):
goal=goal,
strategy=strategy,
description=description,
host_aggregate=host_aggregate).AndReturn(audit_template)
scope=scope).AndReturn(audit_template)
self.mox.ReplayAll()
ret_val = api.watcher.AuditTemplate.create(
self.request, name, goal, strategy,
description, host_aggregate)
description, scope)
self.assertIsInstance(ret_val, dict)
def test_audit_template_patch(self):
@ -210,7 +210,6 @@ class WatcherAPITests(test.APITestCase):
audit = self.api_audits.first()
audit_template_id = self.api_audit_templates.first()['uuid']
deadline = self.api_audits.first()['deadline']
audit_type = self.api_audits.first()['audit_type']
audit_template_uuid = audit_template_id
@ -218,19 +217,17 @@ class WatcherAPITests(test.APITestCase):
watcherclient.audit = self.mox.CreateMockAnything()
watcherclient.audit.create(
audit_template_uuid=audit_template_uuid,
audit_type=audit_type,
deadline=deadline).AndReturn(audit)
audit_type=audit_type).AndReturn(audit)
self.mox.ReplayAll()
ret_val = api.watcher.Audit.create(
self.request, audit_template_uuid, audit_type, deadline)
self.request, audit_template_uuid, audit_type)
self.assertIsInstance(ret_val, dict)
def test_audit_create_with_interval(self):
audit = self.api_audits.list()[1]
audit_template_id = self.api_audit_templates.first()['uuid']
deadline = self.api_audits.first()['deadline']
audit_type = self.api_audits.first()['audit_type']
interval = audit['interval']
audit_template_uuid = audit_template_id
@ -240,12 +237,11 @@ class WatcherAPITests(test.APITestCase):
watcherclient.audit.create(
audit_template_uuid=audit_template_uuid,
audit_type=audit_type,
deadline=deadline,
interval=interval).AndReturn(audit)
self.mox.ReplayAll()
ret_val = api.watcher.Audit.create(
self.request, audit_template_uuid, audit_type, deadline, interval)
self.request, audit_template_uuid, audit_type, interval)
self.assertIsInstance(ret_val, dict)
def test_audit_delete(self):

View File

@ -117,6 +117,7 @@ AVAILABLE_THEMES, DEFAULT_THEME = theme_settings.get_available_themes(
# Dictionary of currently available angular features
ANGULAR_FEATURES = {
'images_panel': False,
'flavors_panel': False,
}
# Notice all customizable configurations should be above this line

View File

@ -84,8 +84,7 @@ def data(TEST):
'uuid': '11111111-1111-1111-1111-111111111111',
'name': 'Audit Template 1',
'description': 'Audit Template 1 description',
'host_aggregate': None,
'extra': {'automatic': False},
'scope': None,
'goal_uuid': 'gggggggg-1111-1111-1111-gggggggggggg',
'strategy_uuid': 'ssssssss-1111-1111-1111-ssssssssssss',
}
@ -93,8 +92,7 @@ def data(TEST):
'uuid': '11111111-2222-2222-2222-111111111111',
'name': 'Audit Template 2',
'description': 'Audit Template 2 description',
'host_aggregate': None,
'extra': {'automatic': False},
'scope': None,
'goal_uuid': 'gggggggg-1111-1111-1111-gggggggggggg',
'strategy_uuid': 'ssssssss-2222-2222-2222-ssssssssssss',
}
@ -102,8 +100,7 @@ def data(TEST):
'uuid': '11111111-3333-3333-3333-111111111111',
'name': 'Audit Template 1',
'description': 'Audit Template 3 description',
'host_aggregate': None,
'extra': {'automatic': False},
'scope': None,
'goal_uuid': 'gggggggg-2222-2222-2222-gggggggggggg',
'strategy_uuid': None,
}
@ -118,14 +115,12 @@ def data(TEST):
TEST.api_audits = utils.TestDataContainer()
audit_dict = {
'uuid': '22222222-2222-2222-2222-222222222222',
'deadline': None,
'audit_type': 'ONESHOT',
'audit_template_uuid': '11111111-1111-1111-1111-111111111111',
'interval': None,
}
audit_dict2 = {
'uuid': '33333333-3333-3333-3333-333333333333',
'deadline': None,
'audit_type': 'CONTINUOUS',
'audit_template_uuid': '11111111-1111-1111-1111-111111111111',
'interval': 60,