Moved the smoke test to the ci repo and cleaned up old tests.

Change-Id: Ic6e24dc1166b8bf7461103b5ebe2d47c14ccf4fa
This commit is contained in:
Tim Kuhlman 2015-06-26 16:21:36 -06:00
parent 0b53dbfb9b
commit 3c0775570a
16 changed files with 35 additions and 1338 deletions

View File

@ -19,7 +19,7 @@
apt: name=python-virtualenv
tasks:
- name: Setup the monasca cli credentials in the default environment
copy: src=tests/env.sh dest=/etc/profile.d/monasca_cli.sh owner=root group=root mode=0644
copy: src=env.sh dest=/etc/profile.d/monasca_cli.sh owner=root group=root mode=0644
tags:
- cli
- name: Update cli

View File

@ -46,7 +46,7 @@
alive_test: ssh
tasks:
- name: Setup the monasca cli credentials in the default environment
copy: src=tests/env.sh dest=/etc/profile.d/monasca_cli.sh owner=root group=root mode=0644
copy: src=env.sh dest=/etc/profile.d/monasca_cli.sh owner=root group=root mode=0644
tags:
- cli
- name: Update cli
@ -82,5 +82,4 @@
roles:
- {role: monasca-default-alarms, tags: [alarms]}
- include: smoke2.yml
- include: smoke.yml

View File

@ -1,8 +1,38 @@
- hosts: mini-mon
sudo: yes
vars:
host_ip: "{{mini_mon}}"
test_base: /opt/monasca/
test_url: https://api.github.com/repos/hpcloud-mon/monasca-ci/tarball/master
tasks:
- name: Fetch the monasca-ci tests
get_url: dest="{{test_base}}/monasca-ci.tar.gz" url="{{test_url}}"
register: download
- name: UI - Uncompress the monasca-ci tar
unarchive: copy=no dest="{{test_base}}" src="{{test_base}}/monasca-ci.tar.gz"
when: download | changed
# In Ansible 1.8 this was in the unarchive output, in 2.0 there is list_files option you can add to unarchive but in 1.9 we must use this
# command to discover the tar dir. This method will work for the other versions also.
- name: Discover the tar path
command: tar -tzf "{{test_base}}/monasca-ci.tar.gz"
register: tarpath
- name: Install the influxdb python library
pip: name=influxdb virtualenv="{{monasca_virtualenv_dir}}"
when: database_type == 'influxdb'
- name: Copy the template locally
fetch: src="{{test_base}}/{{tarpath.stdout_lines[0]}}/tests/smoke/smoke2_configs.py.j2" dest=tests/ flat=yes
- name: Populate config vars
template: src="tests/smoke2_configs.py.j2" dest="{{test_base}}/{{tarpath.stdout_lines[0]}}/tests/smoke/smoke2_configs.py"
- name: Run the smoke2 test
command: "{{monasca_virtualenv_dir}}/bin/python {{test_base}}/{{tarpath.stdout_lines[0]}}/tests/smoke/smoke2.py"
register: smoke2
- debug: var=smoke2.stdout_lines
- name: Run the smoke test
command: /vagrant/tests/smoke.py
environment:
PATH: "{{ansible_env.PATH}}:{{monasca_virtualenv_dir}}/bin"
command: "{{monasca_virtualenv_dir}}/bin/python {{test_base}}/{{tarpath.stdout_lines[0]}}/tests/smoke/smoke.py"
register: smoke
- debug: var=smoke.stdout_lines

View File

@ -1,33 +0,0 @@
- hosts: mini-mon
sudo: yes
vars:
host_ip: "{{mini_mon}}"
test_base: /opt/monasca/
test_url: https://api.github.com/repos/hpcloud-mon/monasca-ci/tarball/master
tasks:
- name: Fetch the monasca-ci tests
get_url: dest="{{test_base}}/monasca-ci.tar.gz" url="{{test_url}}"
register: download
- name: UI - Uncompress the monasca-ci tar
unarchive: copy=no dest="{{test_base}}" src="{{test_base}}/monasca-ci.tar.gz"
when: download | changed
# In Ansible 1.8 this was in the unarchive output, in 2.0 there is list_files option you can add to unarchive but in 1.9 we must use this
# command to discover the tar dir. This method will work for the other versions also.
- name: Discover the tar path
command: tar -tzf "{{test_base}}/monasca-ci.tar.gz"
register: tarpath
- name: Install the influxdb python library
pip: name=influxdb virtualenv="{{monasca_virtualenv_dir}}"
when: database_type == 'influxdb'
- name: Copy the template locally
fetch: src="{{test_base}}/{{tarpath.stdout_lines[0]}}/tests/smoke/smoke2_configs.py.j2" dest=tests/ flat=yes
- name: Populate config vars
template: src="tests/smoke2_configs.py.j2" dest="{{test_base}}/{{tarpath.stdout_lines[0]}}/tests/smoke/smoke2_configs.py"
- name: Run the smoke2 test
command: "{{monasca_virtualenv_dir}}/bin/python {{test_base}}/{{tarpath.stdout_lines[0]}}/tests/smoke/smoke2.py"
register: smoke2
- debug: var=smoke2.stdout_lines

View File

@ -1,3 +0,0 @@
# Test notifications
- run smoke.py from within the mini-mon vm for a full test of Monasca
- env.sh can be sourced to set the proper env for using the python-monascaclient

View File

@ -1,65 +0,0 @@
from __future__ import print_function
import sys
#
"""
Utility methods for CRUD of alarms
"""
def get_state(mon_client, alarm_id):
result = get(mon_client, alarm_id)
return result['state']
def get(mon_client, alarm_id):
result = mon_client.alarms.get(**{'alarm_id': alarm_id})
return result
def disable(mon_client, alarm_id):
patch(mon_client, alarm_id, {'actions_enabled': False})
def enable(mon_client, alarm_id):
patch(mon_client, alarm_id, {'actions_enabled': True})
def set_state(mon_client, alarm_id, state):
patch(mon_client, alarm_id, {'state': state})
new_state = get_state(mon_client, alarm_id)
if new_state != state:
print('Expected new state %s but found %s' %
(state, new_state), file=sys.stderr)
return False
return True
def patch(mon_client, alarm_id, fields):
fields['alarm_id'] = alarm_id
mon_client.alarms.patch(**fields)
def set_optional_field(name, value, fields):
if value is not None:
fields[name] = value
def create(mon_client, name, description, expression, ok_actions=None,
alarm_actions=None, undetermined_actions=None):
fields = {}
fields['name'] = name
fields['expression'] = expression
set_optional_field('description', description, fields)
set_optional_field('ok_actions', ok_actions, fields)
set_optional_field('alarm_actions', alarm_actions, fields)
set_optional_field('undetermined_actions', undetermined_actions, fields)
result = mon_client.alarms.create(**fields)
return result['id']
def find_alarm_byname(mon_client, alarm_name):
alarms = mon_client.alarms.list(**{})
for alarm in alarms:
if alarm['name'] == alarm_name:
return alarm
return None

View File

@ -1,158 +0,0 @@
#!/usr/bin/env python
#
""" Threshold Engine Test
CRUD test
"""
from __future__ import print_function
import sys
import os
import time
import cli_wrapper
import utils
def output_metrics(alarm_id, expected_state, metrics):
print('Generating metrics, waiting for state change to %s' %
expected_state)
hostnames = ['AA', 'BB', 'CC']
for x in range(0, 90):
for metric in metrics:
metric_name = metric[0]
dimensions = metric[1]
args = ['metric-create', '--dimensions']
hostname = hostnames[x % len(hostnames)]
args.append(dimensions + ',' + 'hostname=' + hostname)
args.append(metric_name)
args.append('42')
cli_wrapper.run_mon_cli(args, useJson=False)
state = cli_wrapper.get_alarm_state(alarm_id)
if state == expected_state:
break
time.sleep(1)
if state != expected_state:
print('Did not change to state %s instead was %s in %d seconds' %
(expected_state, state, x), file=sys.stderr)
return False
print('Changed to state %s in %d seconds' % (state, x))
return True
def main():
utils.setup_cli()
alarm_name = 'alarm_crud'
metric_name = 'alarm_crud'
base_dimension = 'service=alarm_test'
expression = 'max(%s{%s}) > 0' % (metric_name, base_dimension)
description = alarm_name + ' Description'
cli_wrapper.delete_alarm_if_exists(alarm_name)
# Add Alarm
alarm_id = cli_wrapper.create_alarm(alarm_name, expression,
description=description)
print('Created Alarm with id %s' % alarm_id)
# Ensure it is created in the right state
initial_state = 'UNDETERMINED'
if not utils.check_alarm_state(alarm_id, initial_state):
return 1
states = []
states.append(initial_state)
# List Alarms, make sure new one shows up
alarm_json = cli_wrapper.find_alarm_by_name(alarm_name)
if alarm_json is None:
print('Did not find alarm named %s using alarm-list' %
alarm_name, file=sys.stderr)
return 1
if alarm_id != alarm_json['id']:
print('Alarm %s has wrong id, expected %s but was %s' %
(alarm_name, alarm_id, alarm_json['id']), file=sys.stderr)
return 1
# Output metrics that will cause it to go ALARM
# Wait for it to change to ALARM
if not output_metrics(alarm_id, 'ALARM', [[metric_name, base_dimension]]):
return 1
states.append('ALARM')
# Modify Alarm by adding new expression that will cause it to go OK
print('Modify Alarm expression so it will go to OK')
new_metric_name = 'other_metric'
new_dimension = 'dim=42'
new_expression = '%s and max(%s{%s}) > 100' % (expression,
new_metric_name,
new_dimension)
alarm_json = cli_wrapper.patch_alarm(alarm_id, '--expression',
new_expression)
if alarm_json['expression'] != new_expression:
print('Did not change expression to %s instead was %s' %
(new_expression, alarm_json['expression']), file=sys.stderr)
return 1
# Output metrics that will cause it to go OK
# Wait for it to change to OK
if not output_metrics(alarm_id, 'OK', [[metric_name, base_dimension],
[new_metric_name, new_dimension]]):
return 1
states.append('OK')
# Modify Alarm by deleting expression that will cause Alarm to go ALARM
print('Delete Alarm sub expression so it will go to ALARM')
cli_wrapper.patch_alarm(alarm_id, '--expression', expression)
# Output metrics that will cause it to go ALARM
# Wait for it to change to ALARM
print('Output extra dimensions to make sure match occurs')
extra_dimension = base_dimension + ',Extra=More'
if not output_metrics(alarm_id, 'ALARM',
[[metric_name, extra_dimension]]):
return 1
states.append('ALARM')
# Modify Alarm by setting alarm state to OK
print('Set Alarm to OK, wait for transition back to ALARM')
cli_wrapper.change_alarm_state(alarm_id, 'OK')
states.append('OK')
# Output metrics that will cause it to go back to ALARM
# Wait for it to change to ALARM
if not output_metrics(alarm_id, 'ALARM',
[[metric_name, base_dimension],
[new_metric_name, new_dimension]]):
return 1
states.append('ALARM')
# Query History
# Delete ALARM
print('Delete alarm')
cli_wrapper.run_mon_cli(['alarm-delete', alarm_id], useJson=False)
# Ensure it can't be queried
if cli_wrapper.find_alarm_by_name(alarm_name) is not None:
print('Still found alarm %s after it was deleted' % alarm_name,
file=sys.stderr)
return 1
# Query History, ensure they still show up
if not utils.check_alarm_history(alarm_id, states):
return 1
# Success
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,110 +0,0 @@
#!/usr/bin/env python
#
""" Wrapper code for running the CLI as a process.
"""
from __future__ import print_function
import sys
import subprocess
import json
import os
def find_obj_for_name(object_json, name):
for obj in object_json:
this_name = obj['name']
if name == this_name:
return obj
return None
def find_alarm_definition_by_name(name):
alarm_json = run_mon_cli(['alarm-definition-list'])
return find_obj_for_name(alarm_json, name)
def delete_alarm_definition_if_exists(name):
alarm_json = find_alarm_definition_by_name(name)
if alarm_json:
run_mon_cli(['alarm-definition-delete', alarm_json['id']],
useJson=False)
def delete_notification_if_exists(notification_name):
notification_json = run_mon_cli(['notification-list'])
notification = find_obj_for_name(notification_json, notification_name)
if notification:
run_mon_cli(['notification-delete', notification['id']], useJson=False)
def run_mon_cli(args, useJson=True):
if useJson:
args.insert(0, '--json')
args.insert(0, 'monasca')
env = os.environ.copy()
env['PYTHONIOENCODING'] = "utf-8"
try:
stdout = subprocess.check_output(args, env=env)
if useJson:
return json.loads(stdout)
else:
return stdout
except subprocess.CalledProcessError as e:
print(e, file=sys.stderr)
sys.exit(1)
def create_notification(notification_name,
notification_addr,
notification_type):
print('Creating notification')
result_json = run_mon_cli(['notification-create', notification_name,
notification_type, notification_addr])
# Parse out id
notification_id = result_json['id']
return notification_id
def get_alarm_state(alarm_id):
result_json = run_mon_cli(['alarm-show', alarm_id])
return result_json['state']
def change_alarm_state(alarm_id, new_state):
print('Changing Alarm state to %s' % new_state)
result_json = run_mon_cli(['alarm-patch', alarm_id, "--state", new_state])
if result_json['state'] != new_state:
print('Alarm patch failed, expected state of %s but was %s' %
(result_json['state'], new_state), file=sys.stderr)
return False
return True
def find_alarms_for_definition(alarm_definition_id):
result_json = run_mon_cli(['alarm-list', "--alarm-definition",
alarm_definition_id])
return [alarm['id'] for alarm in result_json]
def create_alarm_definition(name, expression, description=None,
ok_notif_id=None, alarm_notif_id=None,
undetermined_notif_id=None):
args = ['alarm-definition-create']
add_argument_if_given(args, '--description', description)
add_argument_if_given(args, '--alarm-actions', alarm_notif_id)
add_argument_if_given(args, '--ok-actions', ok_notif_id)
add_argument_if_given(args, '--undetermined-actions',
undetermined_notif_id)
args.append(name)
args.append(expression)
print('Creating alarm definition')
result_json = run_mon_cli(args)
# Parse out id
return result_json['id']
def add_argument_if_given(args, arg, value):
if value is not None:
args.append(arg)
args.append(value)

View File

@ -1,94 +0,0 @@
#!/usr/bin/env python
#
"""measurements
"""
from __future__ import print_function
import sys
import time
import pytz
from datetime import datetime
from monascaclient import client
import monascaclient.exc as exc
import utils
def call_mon_api(method, fields):
try:
resp = method(**fields)
except exc.HTTPException as he:
print(he.code)
print(he.message)
sys.exit(1)
else:
return resp
def create_timestamp(seconds):
utcTimestamp = pytz.utc.localize(datetime.utcfromtimestamp(seconds))
return utcTimestamp.strftime("%Y-%m-%dT%H:%M:%S%z")
def main():
if len(sys.argv) == 1:
print('usage: %s metric_name count' % sys.argv[0], file=sys.stderr)
return 1
mon_client = utils.create_mon_client()
metric_start_time = time.time()
metric_name = sys.argv[1]
num_metrics_to_send = int(sys.argv[2])
dimensions = {'Test_Send': 'Number_1'} # Should be arg
start_time = time.time()
fields = {'name': metric_name}
fields['dimensions'] = dimensions
for val in range(0, num_metrics_to_send):
fields['value'] = str(val)
fields['timestamp'] = time.time()
call_mon_api(mon_client.metrics.create, fields)
# time.sleep(1)
print("Took %d seconds to send %d measurements" %
((time.time() - start_time), num_metrics_to_send))
metric_end_time = time.time()
# API requires end time to be greater than start time
if (metric_end_time - metric_start_time) < 1:
metric_end_time = metric_start_time + 1
start_timestamp = create_timestamp(metric_start_time)
end_timestamp = create_timestamp(metric_end_time)
fields = {'name': metric_name}
fields['dimensions'] = dimensions
fields['start_time'] = start_timestamp
fields['end_time'] = end_timestamp
for i in range(0, 30):
result = call_mon_api(mon_client.metrics.list_measurements, fields)
if len(result) > 0:
measurements = result[0]['measurements']
if len(measurements) >= num_metrics_to_send:
break
print('Found %d of %d metrics so far' %
(len(measurements), num_metrics_to_send))
time.sleep(1)
if len(result) == 0:
print('Did not receive any metrics in %d seconds' % i, file=sys.stderr)
return 1
if len(measurements) != num_metrics_to_send:
print('Expected %d measurements but found %d' %
(num_metrics_to_send, len(measurements)), file=sys.stderr)
return 1
print('Took %d seconds for metrics to fully arrive' % i)
expected = num_metrics_to_send - 1
result = 0
for index in range(num_metrics_to_send, 0):
value = measurements[index]
if value[2] != expected:
print('Expected %d but found %d for %d' %
(expected, value[2], index), file=sys.stderr)
expected = expected - 1
return result
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,32 +0,0 @@
from __future__ import print_function
"""
Utility methods for notifications
"""
def create(mon_client, name, email):
kwargs = {'name': name, 'address': email, 'type': 'EMAIL'}
result = mon_client.notifications.create(**kwargs)
return result['id']
def update(mon_client, notification_id, name, email):
kwargs = {'id': notification_id, 'name': name, 'address': email,
'type': 'EMAIL'}
result = mon_client.notifications.update(**kwargs)
return result['id']
def get(mon_client, notification_id):
kwargs = {'notification_id': notification_id}
result = mon_client.notifications.get(**kwargs)
return result
def find_by_name(mon_client, name):
result = mon_client.notifications.list(**{})
for notification in result:
if notification['name'] == name:
return notification
return None

View File

@ -1,171 +0,0 @@
#!/usr/bin/env python
#
"""Notification Engine Test
CRUD test
"""
from __future__ import print_function
import sys
import os
import time
import notification
import monascaclient.exc as exc
import alarm
import utils
def cycle_states(mon_client, alarm_id, states):
print('Cycling alarm states through %s' % (states))
for state in states:
alarm.set_state(mon_client, alarm_id, state)
def check_notification(alarm_id, user, expected_state, existing):
for i in range(0, 20):
notifications = utils.find_notifications(alarm_id, user)
if len(notifications) > existing:
break
time.sleep(1)
if len(notifications) <= existing:
print('Did not receive new notification in %d seconds for user %s' %
(i+1, user), file=sys.stderr)
return False
if (len(notifications) - existing) > 1:
print('Received %d new notifications instead of 1 for user %s' %
(len(notifications) - existing, user), file=sys.stderr)
return False
new_state = notifications[existing]
if new_state != expected_state:
print('Expected state %s for user %s but found state %s' %
(expected_state, user, new_state), file=sys.stderr)
return False
print('Found notification for state %s for user %s in %d seconds' %
(expected_state, user, i), file=sys.stderr)
return True
def find_or_create_notification(mon_client, name, email):
notif = notification.find_by_name(mon_client, name)
if notif is not None:
if notif['address'] != email:
print('Notification named %s exists but address is %s not %s' %
(name, notif['address'], email), file=sys.stderr)
return None
return notif['id']
else:
return notification.create(mon_client, name, email)
def check_notifications(alarm_id, email1, email2, email3, state1, state2,
state3, existing):
if not check_notification(alarm_id, email1, state1, existing):
return False
if not check_notification(alarm_id, email2, state2, existing):
return False
if not check_notification(alarm_id, email3, state3, existing):
return False
return True
def print_actions(mon_client, state, action_ids):
addresses = []
for action_id in action_ids:
action_notification = notification.get(mon_client, action_id)
addresses.append(action_notification['address'])
print("Notification for %s state sent to %s" % (state, addresses))
def print_notification_setup(mon_client, alarm_id):
alarm_data = alarm.get(mon_client, alarm_id)
print_actions(mon_client, 'ALARM', alarm_data['alarm_actions'])
print_actions(mon_client, 'OK', alarm_data['ok_actions'])
print_actions(mon_client, 'UNDETERMINED',
alarm_data['undetermined_actions'])
def main():
if not utils.ensure_has_notification_engine():
return 1
# Delete notification for OK.Cycle OK, ALARM, UNDETERMINED
# Ensure proper notifications got written for ALARM, UNDETERMINED
states = ['OK', 'ALARM', 'UNDETERMINED']
mon_client = utils.create_mon_client()
try:
# Create 3 notifications with different emails, root, kafka,
# and monasca-agent
email1 = "root"
email2 = "kafka"
email3 = "monasca-agent"
notification_id_1 = find_or_create_notification(mon_client, email1,
email1 + "@localhost")
notification_id_2 = find_or_create_notification(mon_client, email2,
email2 + "@localhost")
notification_id_3 = find_or_create_notification(mon_client, email3,
email3 + "@localhost")
# Create an alarm. Cycle OK, ALARM, UNDETERMINED,
alarm_name = "Test Notifications-" + str(os.getpid())
expr = 'max(not_real_metric{}) > 10'
alarm_id = alarm.create(mon_client, alarm_name, None, expr,
notification_id_1, notification_id_2,
notification_id_3)
print('Created Alarm %s' % alarm_id)
print_notification_setup(mon_client, alarm_id)
print('Test initial cycle of Alarms')
cycle_states(mon_client, alarm_id, states)
# Ensure proper notifications got written to each
if not check_notifications(alarm_id, email1, email2, email3,
states[0], states[1], states[2], 0):
return 1
# Disable alarm. Cycle OK, ALARM, UNDETERMINED,
print('Disable Alarm')
alarm.disable(mon_client, alarm_id)
cycle_states(mon_client, alarm_id, states)
# Ensure no new notifications
if not check_notifications(alarm_id, email1, email2, email3,
states[0], states[1], states[2], 0):
return 1
# Enable alarm. Cycle OK, ALARM, UNDETERMINED
print('Enable Alarm')
alarm.enable(mon_client, alarm_id)
cycle_states(mon_client, alarm_id, states)
# Ensure proper notifications got written to each
if not check_notifications(alarm_id, email1, email2, email3,
states[0], states[1], states[2], 1):
return 1
# Switch Alarm notifications around. Cycle OK, ALARM, UNDETERMINED,
print("Switch around Alarm notifications")
alarm.patch(mon_client, alarm_id,
{'ok_actions': [notification_id_2],
'alarm_actions': [notification_id_3],
'undetermined_actions': [notification_id_1]})
print_notification_setup(mon_client, alarm_id)
cycle_states(mon_client, alarm_id, states)
# Ensure proper notifications got written to each
if not check_notifications(alarm_id, email2, email3, email1,
states[0], states[1], states[2], 2):
return 1
# Switch the email addresses around. Cycle OK, ALARM, UNDETERMINED,
# Ensure proper notifications got written to each
return 0
except exc.HTTPException as he:
print(he.code)
print(he.message)
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,78 +0,0 @@
#!/usr/bin/env python
#
"""Notification Engine Test
Cycle the state of an Alarm the given number of times
"""
from __future__ import print_function
import sys
import time
import notification
import alarm
import utils
def main():
if len(sys.argv) == 1:
print('usage: %s count [alarm-id]' % sys.argv[0], file=sys.stderr)
return 1
if not utils.ensure_has_notification_engine():
return 1
mon_client = utils.create_mon_client()
num_cycles = int(sys.argv[1])
alarm_name = 'notification_cycleTest'
alarm_json = alarm.find_alarm_byname(mon_client, alarm_name)
if alarm_json is not None:
alarm_id = alarm_json['id']
else:
existing = notification.find_by_name(mon_client, alarm_name)
if existing is not None:
notification_id = existing['id']
else:
notification_id = notification.create(mon_client, alarm_name,
"root@localhost")
alarm_id = alarm.create(mon_client, alarm_name, None, 'max(cc) > 100',
notification_id, notification_id,
notification_id)
user = 'root'
start_time = time.time()
initial_state = alarm.get_state(mon_client, alarm_id)
state = initial_state
existing_notifications = utils.find_notifications(alarm_id, user)
notifications_sent = num_cycles * 2
for _ in range(0, notifications_sent):
if state == 'OK':
state = 'ALARM'
else:
state = 'OK'
if not alarm.set_state(mon_client, alarm_id, state):
return 1
print("Took %d seconds to send %d alarm state changes" %
((time.time() - start_time), num_cycles * 2))
for i in range(0, 30):
notifications = utils.find_notifications(alarm_id, user)
notifications_found = len(notifications) - len(existing_notifications)
if notifications_found >= notifications_sent:
break
print('Found %d of %d expected notifications so far' %
(notifications_found, notifications_sent))
time.sleep(1)
if notifications_found < notifications_sent:
print('Expected %d notifications but found %d' %
(notifications_sent, notifications_found), file=sys.stderr)
return 1
print('Took %d seconds for notifications to fully arrive' % i)
result = 0
return result
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,357 +0,0 @@
#!/opt/monasca/bin/python
#
"""smoke
Runs a smoke test of the monitoring installation on mini-mon to ensure
the components (other than the UI) are functioning. The code tests these
components:
1. Agent - ensures metrics are being sent to API
2. API - ensures alarm definitions can created, listed, etc. Ensure
metrics and alarms can be queried
3. CLI - used to talk to the API
4. Persister - ensures metrics and alarm history has been persisted
in database because API can query them
5. Threshold Engine - ensures alarms are created and change state
6. Notification Engine - ensures email notifications are sent to the
local system
This must be run on either the mini-mon VM for the single VM mode or
on the kafka VM in the multi VM mode.
If the tests are to be run in a different environment other than mini-mon,
the environment variables below can be set and the smoke will use those
instead of the mini-mon credentials and settings:
OS_USERNAME
OS_PASSWORD
OS_PROJECT_NAME
OS_AUTH_URL
TODO:
1. Add more logic to give ideas of why a particular step failed, for
example, alarm did not get created because metrics weren't being
received
"""
from __future__ import print_function
import argparse
import sys
import os
import time
import cli_wrapper
import utils
import datetime
import psutil
import smoke_configs
config = smoke_configs.test_config["default"]
# parse command line arguments
def parse_commandline_args():
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', nargs='?', default='default',
help='select configuration <CONFIG>')
return parser.parse_args()
def set_config(config_name):
global config
try:
config = smoke_configs.test_config[config_name]
print('Using {} Configuration'.format(config_name))
return True
except KeyError:
print('Could not find config "{}"'.format(config_name), file=sys.stderr)
return False
def get_metrics(name, dimensions, since):
dimensions_arg = ''
for key, value in dimensions.iteritems():
if dimensions_arg != '':
dimensions_arg = dimensions_arg + ','
dimensions_arg = dimensions_arg + key + '=' + value
return cli_wrapper.run_mon_cli(['measurement-list', '--dimensions',
dimensions_arg, name, since])
def cleanup(notification_name, alarm_definition_name):
cli_wrapper.delete_alarm_definition_if_exists(alarm_definition_name)
cli_wrapper.delete_notification_if_exists(notification_name)
def wait_for_alarm_state_change(alarm_id, old_state):
# Wait for it to change state
print('Waiting for alarm to change state from {}'.format(old_state))
for x in range(0, 250):
time.sleep(1)
state = cli_wrapper.get_alarm_state(alarm_id)
if state != old_state:
print('Alarm state changed to {} in {} seconds'.format(state, x))
return state
print('State never changed from {} in {} seconds'.format(old_state, x),
file=sys.stderr)
return None
def check_notifications(alarm_id, state_changes):
print("Checking Notification Engine")
if not os.path.isfile('/etc/monasca/notification.yaml'):
print('Notification Engine not installed on this VM,' +
' skipping Notifications test',
file=sys.stderr)
return False
notifications = utils.find_notifications(alarm_id, "root")
if len(notifications) != len(state_changes):
print('Expected {} notifications but only found {}'.format(
len(state_changes), len(notifications)), file=sys.stderr)
return False
index = 0
for expected in state_changes:
actual = notifications[index]
if actual != expected:
print('Expected {} but found {} for state change {}'.format(
expected, actual, index+1), file=sys.stderr)
return False
index = index + 1
print('Received email notifications as expected')
return True
def count_metrics(metric_name, metric_dimensions, since):
# Query how many metrics there are for the Alarm
metric_json = get_metrics(metric_name, metric_dimensions, since)
if len(metric_json) == 0:
print('No measurements received for metric {}{} '.format(
metric_name, metric_dimensions), file=sys.stderr)
return None
return len(metric_json[0]['measurements'])
def ensure_at_least(actual, desired):
if actual < desired:
time.sleep(desired - actual)
def wait_for_alarm_creation(alarm_def_id):
print('Waiting for alarm to be created for Alarm Definition {}'.format(alarm_def_id))
for x in range(0, 30):
time.sleep(1)
alarms = cli_wrapper.find_alarms_for_definition(alarm_def_id)
if len(alarms) == 1:
print('Alarm was created in {} seconds'.format(x))
return alarms[0]
elif len(alarms) > 1:
print('{} Alarms were created. Only expected 1'.format(len(alarms)),
file=sys.stderr)
return None
print('Alarm was not created for Alarm Definition {} in {} seconds'.format(
alarm_def_id, x), file=sys.stderr)
return None
def smoke_test():
notification_name = config['notification']['name']
notification_addr = config['notification']['addr']
notification_type = config['notification']['type']
alarm_definition_name = config['alarm']['name']
metric_name = config['metric']['name']
metric_dimensions = config['metric']['dimensions']
statsd_metric_name = config['statsd_metric']['name']
statsd_metric_dimensions = config['statsd_metric']['dimensions']
cleanup(notification_name, alarm_definition_name)
# Query how many metrics there are for the Alarm
hour_ago = datetime.datetime.now() - datetime.timedelta(hours=1)
hour_ago_str = hour_ago.strftime('%Y-%m-%dT%H:%M:%S')
print('Getting metrics for {}{} '.format(metric_name, metric_dimensions))
initial_num_metrics = count_metrics(metric_name, metric_dimensions,
hour_ago_str)
if initial_num_metrics is None or initial_num_metrics == 0:
msg = ('No metric {} with dimensions {} received in last hour'.format(
metric_name, metric_dimensions))
return False, msg
print('Getting metrics for {}{} '.format(statsd_metric_name, statsd_metric_dimensions))
initial_statsd_num_metrics = count_metrics(statsd_metric_name, statsd_metric_dimensions, hour_ago_str)
# statsd metrics may not have been sent yet, which will return None from the CLI wrapper
if initial_statsd_num_metrics is None:
initial_statsd_num_metrics = 0
start_time = time.time()
# Create Notification through CLI
notif_id = cli_wrapper.create_notification(notification_name,
notification_addr,
notification_type)
# Create Alarm through CLI
expression = config['alarm']['expression']
description = config['alarm']['description']
alarm_def_id = cli_wrapper.create_alarm_definition(
alarm_definition_name,
expression,
description=description,
ok_notif_id=notif_id,
alarm_notif_id=notif_id,
undetermined_notif_id=notif_id)
# Wait for an alarm to be created
alarm_id = wait_for_alarm_creation(alarm_def_id)
if alarm_id is None:
received_num_metrics = count_metrics(metric_name, metric_dimensions,
hour_ago_str)
if received_num_metrics == initial_num_metrics:
print('Did not receive any {}{} metrics while waiting'.format(metric_name,metric_dimensions))
else:
delta = received_num_metrics - initial_num_metrics
print('Received {} {} metrics while waiting'.format(delta, metric_name))
return False, 'Alarm creation error'
# Ensure it is created in the right state
initial_state = 'UNDETERMINED'
if not utils.check_alarm_state(alarm_id, initial_state):
msg = 'Alarm is in an invalid initial state'
return False, msg
states = []
states.append(initial_state)
state = wait_for_alarm_state_change(alarm_id, initial_state)
if state is None:
msg = 'Alarm is in an invalid state'
return False, msg
if state != 'ALARM':
print('Wrong final state, expected ALARM but was {}'.format(state),
file=sys.stderr)
msg = 'Alarm is in an invalid final state'
return False, msg
states.append(state)
new_state = 'OK'
states.append(new_state)
if not cli_wrapper.change_alarm_state(alarm_id, new_state):
msg = 'Unable to change Alarm state'
return False, msg
# There is a bug in the API which allows this to work. Soon that
# will be fixed and this will fail
if len(sys.argv) > 1:
final_state = 'ALARM'
states.append(final_state)
state = wait_for_alarm_state_change(alarm_id, new_state)
if state is None:
msg = 'Alarm is in an unknown state'
return False, msg
if state != final_state:
msg = ('Wrong final state, expected {} but was {}'.format(final_state, state))
return False, msg
# If the alarm changes state too fast, then there isn't time for the new
# metric to arrive. Unlikely, but it has been seen
ensure_at_least(time.time() - start_time, 35)
change_time = time.time() - start_time
final_num_metrics = count_metrics(metric_name, metric_dimensions,
hour_ago_str)
if final_num_metrics <= initial_num_metrics:
msg = ('No new metrics received for {}{} in {} seconds'.format(metric_name, metric_dimensions, change_time))
return False, msg
print('Received {} metrics in {} seconds'.format((final_num_metrics - initial_num_metrics), change_time))
if not utils.check_alarm_history(alarm_id, states):
msg = 'Invalid alarm history'
return False, msg
# Notifications are only sent out for the changes, so omit the first state
if not check_notifications(alarm_id, states[1:]):
msg = 'Could not find correct notifications for alarm {}'.format(alarm_id)
return False, msg
# Check that monasca statsd is sending metrics
# Metrics may take some time to arrive
print('Waiting for statsd metrics')
for x in range(0,30):
final_statsd_num_metrics = count_metrics(statsd_metric_name, statsd_metric_dimensions, hour_ago_str)
if final_statsd_num_metrics > initial_statsd_num_metrics:
break
if x >= 29:
msg = 'No metrics received for statsd metric {}{} in {} seconds'.format(
statsd_metric_name, statsd_metric_dimensions, time.time() - start_time)
return False, msg
time.sleep(1)
print('Received {0} metrics for {1}{2} in {3} seconds'.format(final_statsd_num_metrics - initial_statsd_num_metrics,
statsd_metric_name,
statsd_metric_dimensions,
time.time() - start_time))
msg = ''
return True, msg
def find_processes():
"""Find_process is meant to validate that all the required processes
are running before starting the smoke test """
process_missing = []
process_list = config['system_vars']['expected_processes']
for process in process_list:
process_found_flag = False
for item in psutil.process_iter():
for cmd in item.cmdline():
if process in cmd:
process_found_flag = True
break
if not process_found_flag:
process_missing.append(process)
if len(process_missing) > 0: # if processes were not found
print ('Process = {} Not Found'.format(process_missing))
return False
else:
print ('All Mini-Mon Processes Found')
return True
def main():
# May be able to delete this test because the find_process check should
# validate the notification engine present.
if not utils.ensure_has_notification_engine():
return 1
utils.setup_cli()
# parse the command line arguments
cmd_args = parse_commandline_args()
if not set_config(cmd_args.config):
return 1
print('*****VERIFYING HOST ENVIRONMENT*****')
if find_processes():
print('*****BEGIN TEST*****')
complete, msg = smoke_test()
if not complete:
print('*****TEST FAILED*****', file=sys.stderr)
print(msg, file=sys.stderr)
return 1
else:
return 1
cleanup(config['notification']['name'], config['alarm']['name'])
print('*****TEST COMPLETE*****')
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,74 +0,0 @@
# -*- encoding: utf-8 -*-
"""configurations for smoke test"""
import subprocess
system_vars = {
'default': { # the default configuration, assumes monasca-vagrant setup
'expected_processes': ('monasca-persister', 'monasca-notification',
'kafka', 'zookeeper.jar', 'monasca-api',
'apache-storm', 'mysqld'),
'mail_host': 'localhost',
'metric_host': subprocess.check_output(['hostname', '-f']).strip()},
}
test_config = {
'default': { # the default configuration,
# simple test of each component of monasca-vagrant
'system_vars': system_vars['default'],
'notification': {
'name': u'Monasca Smoke Test Ā',
'addr': 'root@'+system_vars['default']['mail_host'],
'type': 'EMAIL'},
'alarm': {
'name': u'high cpu and load Ա',
'expression': 'max(cpu.system_perc) > 0 and ' +
'max(load.avg_1_min{hostname=' +
system_vars['default']['metric_host'] +
'}) > 0',
'description': u'System CPU Utilization exceeds 1% and ' +
u'Load exceeds 3 per measurement period ἀ'},
'metric': {
'name': 'load.avg_1_min',
'dimensions': {'hostname':
system_vars['default']['metric_host']}},
'statsd_metric': {
'name': 'monasca.sent_smtp_count',
'dimensions': {'hostname':
system_vars['default']['metric_host']}}
},
'webhook': {
'system_vars': system_vars['default'],
'notification': {
'name': 'Smoke Test Webhook',
'addr': 'http://127.0.0.1:8080',
'type': 'WEBHOOK'},
'alarm': {
'name': 'high cpu and load',
'expression': 'max(cpu.system_perc) > 0 and ' +
'max(load.avg_1_min{hostname=' +
system_vars['default']['metric_host'] +
'}) > 0',
'description': 'System CPU Utilization exceeds 1% and ' +
'Load exceeds 3 per measurement period'},
'metric': {
'name': 'load.avg_1_min',
'dimensions': {'hostname':
system_vars['default']['metric_host']}},
'statsd_metric': {
'name': 'monasca.sent_smtp_count',
'dimensions': {'hostname':
system_vars['default']['metric_host']}}
}
}

View File

@ -1,157 +0,0 @@
from __future__ import print_function
import cli_wrapper
import os
import re
import subprocess
import sys
import time
from monascaclient import client
from monasca_agent.common.keystone import Keystone
"""
Utility methods for testing
"""
OS_USERNAME = 'mini-mon'
OS_PASSWORD = 'password'
OS_PROJECT_NAME = 'mini-mon'
OS_AUTH_URL = 'http://192.168.10.5:35357/v3/'
def check_alarm_history(alarm_id, states):
transitions = len(states) - 1
print('Checking Alarm History')
# May take some time for Alarm history to flow all the way through
for _ in range(0, 20):
result_json = cli_wrapper.run_mon_cli(['alarm-history', alarm_id])
if len(result_json) >= transitions:
break
time.sleep(4)
result = True
if transitions != len(result_json):
print('Wrong number of history entries, expected %d but was %d' %
(transitions, len(result_json)), file=sys.stderr)
return False
# Alarm history is sorted by date
index = 0
for i in range(0, transitions):
old_state = states[i]
new_state = states[i+1]
alarm_json = result_json[index]
if not check_expected(old_state, alarm_json['old_state'], 'old_state',
i):
result = False
if not check_expected(new_state, alarm_json['new_state'], 'new_state',
i):
result = False
if not check_expected(alarm_id, alarm_json['alarm_id'], 'alarm_id',
i):
result = False
index = index + 1
if result:
print('Alarm History is OK')
return result
def check_expected(expected, actual, what, index):
if (expected == actual):
return True
print('Wrong %s for alarm history expected %s but was %s transition %d' %
(what, expected, actual, index+1), file=sys.stderr)
return False
def check_alarm_state(alarm_id, expected):
state = cli_wrapper.get_alarm_state(alarm_id)
if state != expected:
print('Wrong initial alarm state, expected %s but is %s' %
(expected, state))
return False
return True
def get_api_host():
# Determine if we are running on multiple VMs or just the one
if os.path.isfile('/etc/mon/mon-api-config.yml'):
return 'localhost'
else:
return '192.168.10.4'
def set_if_not_env(name, default):
if name not in os.environ:
os.environ[name] = default
elif default != os.environ[name]:
print('%s already set to %s' % (name, os.environ[name]))
def setup_cli():
api_host = get_api_host()
# These need to be set because we are invoking the CLI as a process
set_if_not_env('OS_USERNAME', OS_USERNAME)
set_if_not_env('OS_PASSWORD', OS_PASSWORD)
set_if_not_env('OS_PROJECT_NAME', OS_PROJECT_NAME)
set_if_not_env('OS_AUTH_URL', OS_AUTH_URL)
os.environ['http_proxy'] = ''
os.environ['https_proxy'] = ''
os.environ['HTTP_PROXY'] = ''
os.environ['HTTPS_PROXY'] = ''
def create_mon_client():
api_host = get_api_host()
token = get_token(OS_USERNAME, OS_PASSWORD, OS_PROJECT_NAME,
OS_AUTH_URL)
api_version = '2_0'
endpoint = 'http://' + api_host + ':8080/v2.0'
kwargs = {'token': token}
return client.Client(api_version, endpoint, **kwargs)
def get_token(os_username, os_password, os_project_name, os_auth_url):
kwargs = { 'keystone_url': os_auth_url,
'username': os_username,
'password': os_password,
'project_domain_name':os_project_name}
keystone = Keystone(kwargs)
return keystone.refresh_token()
def ensure_has_notification_engine():
if not os.path.isfile('/etc/monasca/notification.yaml'):
print('Must be run on a VM with Notification Engine installed',
file=sys.stderr)
return False
return True
def find_notifications(alarm_id, user):
args = ['sudo', 'cat', '/var/mail/' + user]
result = []
env = os.environ.copy()
env['PYTHONIOENCODING'] = "utf-8"
try:
stdout = subprocess.check_output(args, env=env)
except subprocess.CalledProcessError as e:
print(e, file=sys.stderr)
sys.exit(1)
previous = ''
for line in stdout.splitlines():
# Get the state; the alarm_id always follows the state message
line = unicode(line, "utf-8")
if alarm_id in line:
""" In the notification message the state verb is framed by
'transitioned to the ' and ' state'
"""
result.append(re.search('transitioned to the (.+?) state',
previous).group(1))
previous = line
return result