diff --git a/requirements.txt b/requirements.txt index 9843757..8ec0426 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ pbr>=1.6 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT +PyYAML>=3.10.0 # MIT diff --git a/watcherclient/common/command.py b/watcherclient/common/command.py index fee4559..fede00a 100644 --- a/watcherclient/common/command.py +++ b/watcherclient/common/command.py @@ -43,4 +43,8 @@ class Lister(Command, lister.Lister): class ShowOne(Command, show.ShowOne): - pass + def get_parser(self, prog_name, formatter_class=None): + parser = super(ShowOne, self).get_parser(prog_name) + if formatter_class: + parser.formatter_class = formatter_class + return parser diff --git a/watcherclient/common/utils.py b/watcherclient/common/utils.py index 990818e..ac75554 100644 --- a/watcherclient/common/utils.py +++ b/watcherclient/common/utils.py @@ -19,7 +19,9 @@ from __future__ import print_function import argparse import json +import os import uuid +import yaml from oslo_utils import importutils @@ -192,3 +194,10 @@ def is_uuid_like(val): return str(uuid.UUID(val)) == val except (TypeError, ValueError, AttributeError): return False + + +def serialize_file_to_dict(filename): + filename = os.path.expanduser(filename) + with open(filename, "rb") as stream: + scope = yaml.safe_load(stream.read()) + return scope diff --git a/watcherclient/tests/v1/test_audit_shell.py b/watcherclient/tests/v1/test_audit_shell.py index cc6e1db..aeac4f1 100644 --- a/watcherclient/tests/v1/test_audit_shell.py +++ b/watcherclient/tests/v1/test_audit_shell.py @@ -29,7 +29,6 @@ AUDIT_TEMPLATE_1 = { 'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', 'name': 'at1', 'description': 'Audit Template 1 description', - 'host_aggregate': 5, 'extra': {'automatic': False}, 'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836', 'strategy_uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5', @@ -64,7 +63,6 @@ AUDIT_1 = { 'state': 'PENDING', 'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', 'audit_template_name': 'at1', - 'host_aggregate': 5, 'goal_name': 'SERVER_CONSOLIDATION', 'strategy_name': 'basic', 'created_at': datetime.datetime.now().isoformat(), @@ -72,6 +70,7 @@ AUDIT_1 = { 'deleted_at': None, 'parameters': None, 'interval': None, + 'scope': '', } AUDIT_2 = { @@ -81,7 +80,6 @@ AUDIT_2 = { 'state': 'PENDING', 'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', 'audit_template_name': 'at1', - 'host_aggregate': None, 'goal_name': 'fc087747-61be-4aad-8126-b701731ae836', 'strategy_name': None, 'created_at': datetime.datetime.now().isoformat(), @@ -89,6 +87,7 @@ AUDIT_2 = { 'deleted_at': None, 'parameters': None, 'interval': None, + 'scope': '', } AUDIT_3 = { @@ -98,7 +97,6 @@ AUDIT_3 = { 'state': 'PENDING', 'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', 'audit_template_name': 'at1', - 'host_aggregate': 3, 'goal_name': None, 'strategy_name': None, 'created_at': datetime.datetime.now().isoformat(), @@ -106,6 +104,7 @@ AUDIT_3 = { 'deleted_at': None, 'parameters': None, 'interval': 3600, + 'scope': '', } diff --git a/watcherclient/tests/v1/test_audit_template.py b/watcherclient/tests/v1/test_audit_template.py index 3537ed6..a628180 100644 --- a/watcherclient/tests/v1/test_audit_template.py +++ b/watcherclient/tests/v1/test_audit_template.py @@ -28,7 +28,6 @@ AUDIT_TMPL1 = { 'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', 'name': 'Audit Template 1', 'description': 'Audit Template 1 description', - 'host_aggregate': 5, 'extra': {'automatic': False}, 'goal_uuid': '7568667b-51fe-4087-9eb1-29b26891036f', 'goal_name': 'SERVER_CONSOLIDATION', @@ -41,7 +40,6 @@ AUDIT_TMPL2 = { 'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea', 'name': 'Audit Template 2', 'description': 'Audit Template 2 description', - 'host_aggregate': 8, 'extra': {'automatic': True}, 'goal_uuid': 'e75ee410-b32b-465f-88b5-4397705f9473', 'goal_name': 'DUMMY', @@ -54,7 +52,6 @@ AUDIT_TMPL3 = { 'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588', 'name': 'Audit Template 3', 'description': 'Audit Template 3 description', - 'host_aggregate': 7, 'extra': {'automatic': True}, 'goal_uuid': '7568667b-51fe-4087-9eb1-29b26891036f', 'goal_name': 'SERVER_CONSOLIDATION', @@ -407,8 +404,6 @@ class AuditTemplateManagerTest(utils.BaseTestCase): self.assertEqual(AUDIT_TMPL1['name'], audit_template.name) self.assertEqual(AUDIT_TMPL1['description'], audit_template.description) - self.assertEqual(AUDIT_TMPL1['host_aggregate'], - audit_template.host_aggregate) self.assertEqual(AUDIT_TMPL1['goal_uuid'], audit_template.goal_uuid) self.assertEqual(AUDIT_TMPL1['strategy_uuid'], audit_template.strategy_uuid) @@ -427,8 +422,6 @@ class AuditTemplateManagerTest(utils.BaseTestCase): self.assertEqual(AUDIT_TMPL1['name'], audit_template.name) self.assertEqual(AUDIT_TMPL1['description'], audit_template.description) - self.assertEqual(AUDIT_TMPL1['host_aggregate'], - audit_template.host_aggregate) self.assertEqual(AUDIT_TMPL1['goal_uuid'], audit_template.goal_uuid) self.assertEqual(AUDIT_TMPL1['strategy_uuid'], audit_template.strategy_uuid) diff --git a/watcherclient/tests/v1/test_audit_template_shell.py b/watcherclient/tests/v1/test_audit_template_shell.py index a2845a0..ce93c60 100644 --- a/watcherclient/tests/v1/test_audit_template_shell.py +++ b/watcherclient/tests/v1/test_audit_template_shell.py @@ -47,7 +47,6 @@ AUDIT_TEMPLATE_1 = { 'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', 'name': 'at1', 'description': 'Audit Template 1 description', - 'host_aggregate': 5, 'extra': {'automatic': False}, 'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836', 'goal_name': 'SERVER_CONSOLIDATION', @@ -56,13 +55,13 @@ AUDIT_TEMPLATE_1 = { 'created_at': datetime.datetime.now().isoformat(), 'updated_at': None, 'deleted_at': None, + 'scope': [] } AUDIT_TEMPLATE_2 = { 'uuid': '2a60ca9b-09b0-40ff-8674-de8a36fc4bc8', 'name': 'at2', 'description': 'Audit Template 2', - 'host_aggregate': 3, 'extra': {'automatic': False}, 'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836', 'goal_name': 'SERVER_CONSOLIDATION', @@ -71,6 +70,7 @@ AUDIT_TEMPLATE_2 = { 'created_at': datetime.datetime.now().isoformat(), 'updated_at': None, 'deleted_at': None, + 'scope': [] } @@ -299,7 +299,7 @@ class AuditTemplateShellTest(base.CommandTestCase): self.m_audit_template_mgr.update.return_value = audit_template exit_code, result = self.run_cmd( - 'audittemplate update at1 replace host_aggregate=5') + 'audittemplate update at1 replace description="New description"') self.assertEqual(0, exit_code) self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS, @@ -307,7 +307,8 @@ class AuditTemplateShellTest(base.CommandTestCase): result) self.m_audit_template_mgr.update.assert_called_once_with( 'at1', - [{'op': 'replace', 'path': '/host_aggregate', 'value': 5}]) + [{'op': 'replace', 'path': '/description', + 'value': 'New description'}]) def test_do_audit_template_create(self): audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1) @@ -346,8 +347,7 @@ class AuditTemplateShellTest(base.CommandTestCase): self.m_audit_template_mgr.create.return_value = audit_template exit_code, result = self.run_cmd( - 'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836 ' - '-a 5') + 'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836') self.assertEqual(0, exit_code) self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS, @@ -355,8 +355,7 @@ class AuditTemplateShellTest(base.CommandTestCase): result) self.m_audit_template_mgr.create.assert_called_once_with( goal='fc087747-61be-4aad-8126-b701731ae836', - name='at1', - host_aggregate='5') + name='at1') def test_do_audit_template_create_with_extra(self): audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1) diff --git a/watcherclient/v1/audit.py b/watcherclient/v1/audit.py index 94edfa3..ad19b0e 100644 --- a/watcherclient/v1/audit.py +++ b/watcherclient/v1/audit.py @@ -19,9 +19,8 @@ from watcherclient.common import utils from watcherclient import exceptions as exc -CREATION_ATTRIBUTES = ['audit_template_uuid', 'host_aggregate', - 'deadline', 'audit_type', 'interval', - 'parameters', 'goal', 'strategy'] +CREATION_ATTRIBUTES = ['audit_template_uuid', 'deadline', 'audit_type', + 'interval', 'parameters', 'goal', 'strategy'] class Audit(base.Resource): diff --git a/watcherclient/v1/audit_shell.py b/watcherclient/v1/audit_shell.py index 1d37b20..f1bf65c 100644 --- a/watcherclient/v1/audit_shell.py +++ b/watcherclient/v1/audit_shell.py @@ -162,12 +162,6 @@ class CreateAudit(command.ShowOne): dest='strategy', metavar='', help=_('Strategy UUID or name associated to this audit.')) - parser.add_argument( - '-r', '--host-aggregate', - dest='host_aggregate', - metavar='', - help=_('Name or UUID of the host aggregate targeted ' - 'by this audit.')) parser.add_argument( '-a', '--audit-template', dest='audit_template_uuid', @@ -178,9 +172,8 @@ class CreateAudit(command.ShowOne): def take_action(self, parsed_args): client = getattr(self.app.client_manager, "infra-optim") - field_list = ['audit_template_uuid', 'host_aggregate', - 'audit_type', 'deadline', 'parameters', 'interval', - 'goal', 'strategy'] + field_list = ['audit_template_uuid', 'audit_type', 'deadline', + 'parameters', 'interval', 'goal', 'strategy'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) diff --git a/watcherclient/v1/audit_template.py b/watcherclient/v1/audit_template.py index aede8fc..7909c8b 100644 --- a/watcherclient/v1/audit_template.py +++ b/watcherclient/v1/audit_template.py @@ -18,8 +18,8 @@ from watcherclient.common import base from watcherclient.common import utils from watcherclient import exceptions as exc -CREATION_ATTRIBUTES = ['host_aggregate', 'description', 'name', - 'extra', 'goal', 'strategy'] +CREATION_ATTRIBUTES = ['description', 'name', 'extra', 'goal', 'strategy', + 'scope'] class AuditTemplate(base.Resource): diff --git a/watcherclient/v1/audit_template_shell.py b/watcherclient/v1/audit_template_shell.py index f7bb553..01ff3ee 100644 --- a/watcherclient/v1/audit_template_shell.py +++ b/watcherclient/v1/audit_template_shell.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse + from osc_lib import utils from oslo_utils import uuidutils @@ -126,7 +128,17 @@ class CreateAuditTemplate(command.ShowOne): """Create new audit template.""" def get_parser(self, prog_name): - parser = super(CreateAuditTemplate, self).get_parser(prog_name) + class SmartFormatter(argparse.HelpFormatter): + + def _split_lines(self, text, width): + if '\n' in text: + return text.splitlines() + else: + return argparse.HelpFormatter._split_lines( + self, text, width) + + parser = super(CreateAuditTemplate, self).get_parser( + prog_name, formatter_class=SmartFormatter) parser.add_argument( 'name', metavar='', @@ -151,19 +163,53 @@ class CreateAuditTemplate(command.ShowOne): help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) parser.add_argument( - '-a', '--host-aggregate', - dest='host_aggregate', - metavar='', - help=_('Name or UUID of the host aggregate targeted ' - 'by this audit template.')) + '--scope', + metavar='', + help=_("Part of the cluster on which an audit will be done.\n" + "Can be provided either in yaml or json file.\n" + "YAML example:\n" + "---\n" + " - host_aggregates:\n" + " - id: 1\n" + " - id: 2\n" + " - id: 3\n" + " - availability_zones:\n" + " - name: AZ1\n" + " - name: AZ2\n" + " - exclude:\n" + " - instances:\n" + " - uuid: UUID1\n" + " - uuid: UUID2\n" + " - compute_nodes:\n" + " - name: compute1\n" + "\n" + "JSON example:\n" + "[{'host_aggregates': [\n" + " {'id': 1},\n" + " {'id': 2},\n" + " {'id': 3}]},\n" + " {'availability_zones': [\n" + " {'name': 'AZ1'},\n" + " {'name': 'AZ2'}]},\n" + " {'exclude': [\n" + " {'instances': [\n" + " {'uuid': 'UUID1'},\n" + " {'uuid': 'UUID2'}\n" + " ]},\n" + " {'compute_nodes': [\n" + " {'name': 'compute1'}\n" + " ]}\n" + "]}]\n" + ) + ) return parser def take_action(self, parsed_args): client = getattr(self.app.client_manager, "infra-optim") - field_list = ['host_aggregate', 'description', 'name', 'extra', - 'goal', 'strategy'] + field_list = ['description', 'name', 'extra', 'goal', 'strategy', + 'scope'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) @@ -176,6 +222,9 @@ class CreateAuditTemplate(command.ShowOne): if not uuidutils.is_uuid_like(fields['strategy']): fields['strategy'] = client.strategy.get( fields['strategy']).uuid + if fields.get('scope'): + fields['scope'] = common_utils.serialize_file_to_dict( + fields['scope']) fields = common_utils.args_array_to_dict(fields, 'extra') audit_template = client.audit_template.create(**fields) diff --git a/watcherclient/v1/resource_fields.py b/watcherclient/v1/resource_fields.py index e90d79f..138b718 100644 --- a/watcherclient/v1/resource_fields.py +++ b/watcherclient/v1/resource_fields.py @@ -19,13 +19,11 @@ # Audit Template AUDIT_TEMPLATE_FIELDS = [ 'uuid', 'created_at', 'updated_at', 'deleted_at', - 'description', 'host_aggregate', 'name', - 'extra', 'goal_name', 'strategy_name'] + 'description', 'name', 'extra', 'goal_name', 'strategy_name', 'scope'] AUDIT_TEMPLATE_FIELD_LABELS = [ 'UUID', 'Created At', 'Updated At', 'Deleted At', - 'Description', 'Host Aggregate ID or Name', 'Name', - 'Extra', 'Goal', 'Strategy'] + 'Description', 'Name', 'Extra', 'Goal', 'Strategy', 'Audit Scope'] AUDIT_TEMPLATE_SHORT_LIST_FIELDS = [ 'uuid', 'name', 'goal_name', 'strategy_name'] @@ -35,13 +33,13 @@ AUDIT_TEMPLATE_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Goal', 'Strategy'] # Audit AUDIT_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at', 'deadline', 'state', 'audit_type', - 'parameters', 'interval', - 'host_aggregate', 'goal_name', 'strategy_name'] + 'parameters', 'interval', 'goal_name', 'strategy_name', + 'scope'] AUDIT_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At', 'Deadline', 'State', 'Audit Type', - 'Parameters', 'Interval', 'Host Aggregate ID or Name', - 'Goal', 'Strategy'] + 'Parameters', 'Interval', 'Goal', 'Strategy', + 'Audit Scope'] AUDIT_SHORT_LIST_FIELDS = ['uuid', 'audit_type', 'state', 'goal_name', 'strategy_name']