Zack Williams | 41513bf | 2018-07-07 20:08:35 -0700 | [diff] [blame] | 1 | # Copyright 2017-present Open Networking Foundation |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 14 | from unittest import main |
| 15 | from common.utils.consulhelpers import get_endpoint_from_consul |
Richard Jankowski | 8728934 | 2018-04-19 13:59:10 -0400 | [diff] [blame] | 16 | from tests.itests.test_utils import get_pod_ip, \ |
Stephane Barbarie | cc6b2e6 | 2017-03-02 14:35:55 -0500 | [diff] [blame] | 17 | run_long_running_command_with_timeout |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 18 | from tests.itests.voltha.rest_base import RestBase |
| 19 | from google.protobuf.json_format import MessageToDict |
| 20 | from voltha.protos.device_pb2 import Device |
| 21 | import simplejson, jsonschema |
| 22 | import re |
Richard Jankowski | 8728934 | 2018-04-19 13:59:10 -0400 | [diff] [blame] | 23 | from tests.itests.orch_environment import get_orch_environment |
| 24 | from testconfig import config |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 25 | |
| 26 | # ~~~~~~~ Common variables ~~~~~~~ |
| 27 | |
| 28 | LOCAL_CONSUL = "localhost:8500" |
Richard Jankowski | 8728934 | 2018-04-19 13:59:10 -0400 | [diff] [blame] | 29 | ENV_DOCKER_COMPOSE = 'docker-compose' |
| 30 | ENV_K8S_SINGLE_NODE = 'k8s-single-node' |
| 31 | |
| 32 | orch_env = ENV_DOCKER_COMPOSE |
| 33 | if 'test_parameters' in config and 'orch_env' in config['test_parameters']: |
| 34 | orch_env = config['test_parameters']['orch_env'] |
| 35 | print 'orchestration-environment: %s' % orch_env |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 36 | |
| 37 | COMMANDS = dict( |
| 38 | kafka_client_run="kafkacat -b {} -L", |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 39 | kafka_client_send_msg='echo hello | kafkacat -b {} -P -t voltha.alarms -c 1', |
| 40 | kafka_client_alarm_check="kafkacat -o end -b {} -C -t voltha.alarms -c 2", |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 41 | ) |
| 42 | |
| 43 | ALARM_SCHEMA = { |
| 44 | "type": "object", |
| 45 | "properties": { |
| 46 | "id": {"type": "string"}, |
| 47 | "type": {"type": "string"}, |
| 48 | "category": {"type": "string"}, |
| 49 | "state": {"type": "string"}, |
| 50 | "severity": {"type": "string"}, |
| 51 | "resource_id": {"type": "string"}, |
| 52 | "raised_ts": {"type": "number"}, |
| 53 | "reported_ts": {"type": "number"}, |
| 54 | "changed_ts": {"type": "number"}, |
| 55 | "description": {"type": "string"}, |
| 56 | "context": { |
| 57 | "type": "object", |
| 58 | "additionalProperties": {"type": "string"} |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | |
Stephane Barbarie | cc6b2e6 | 2017-03-02 14:35:55 -0500 | [diff] [blame] | 64 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 65 | |
| 66 | |
| 67 | class VolthaAlarmEventTests(RestBase): |
Richard Jankowski | 8728934 | 2018-04-19 13:59:10 -0400 | [diff] [blame] | 68 | # Get endpoint info |
| 69 | if orch_env == ENV_K8S_SINGLE_NODE: |
| 70 | rest_endpoint = get_pod_ip('voltha') + ':8443' |
| 71 | kafka_endpoint = get_pod_ip('kafka') |
| 72 | else: |
| 73 | rest_endpoint = get_endpoint_from_consul(LOCAL_CONSUL, 'voltha-envoy-8443') |
| 74 | kafka_endpoint = get_endpoint_from_consul(LOCAL_CONSUL, 'kafka') |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 75 | |
| 76 | # Construct the base_url |
ubuntu | c5c83d7 | 2017-07-01 17:57:19 -0700 | [diff] [blame] | 77 | base_url = 'https://' + rest_endpoint |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 78 | |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 79 | # ~~~~~~~~~~~~ Tests ~~~~~~~~~~~~ |
| 80 | |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 81 | def test_1_alarm_topic_exists(self): |
| 82 | # Produce a message to ensure that the topic exists |
| 83 | cmd = COMMANDS['kafka_client_send_msg'].format(self.kafka_endpoint) |
| 84 | run_long_running_command_with_timeout(cmd, 5) |
| 85 | |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 86 | # We want to make sure that the topic is available on the system |
| 87 | expected_pattern = ['voltha.alarms'] |
| 88 | |
| 89 | # Start the kafka client to retrieve details on topics |
| 90 | cmd = COMMANDS['kafka_client_run'].format(self.kafka_endpoint) |
| 91 | kafka_client_output = run_long_running_command_with_timeout(cmd, 20) |
| 92 | |
| 93 | # Loop through the kafka client output to find the topic |
| 94 | found = False |
| 95 | for out in kafka_client_output: |
| 96 | if all(ep in out for ep in expected_pattern): |
| 97 | found = True |
| 98 | break |
| 99 | |
Stephane Barbarie | cc6b2e6 | 2017-03-02 14:35:55 -0500 | [diff] [blame] | 100 | self.assertTrue(found, |
| 101 | 'Failed to find topic {}'.format(expected_pattern)) |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 102 | |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 103 | def test_2_alarm_generated_by_adapter(self): |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 104 | # Verify that REST calls can be made |
| 105 | self.verify_rest() |
| 106 | |
| 107 | # Create a new device |
| 108 | device = self.add_device() |
| 109 | |
| 110 | # Activate the new device |
| 111 | self.activate_device(device['id']) |
| 112 | |
| 113 | # The simulated olt device should start generating alarms periodically |
| 114 | alarm = self.get_alarm_event(device['id']) |
| 115 | |
| 116 | # Make sure that the schema is valid |
| 117 | self.validate_alarm_event_schema(alarm) |
| 118 | |
| 119 | # Validate the constructed alarm id |
| 120 | self.verify_alarm_event_id(device['id'], alarm['id']) |
| 121 | |
| 122 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 123 | |
| 124 | # Make sure the Voltha REST interface is available |
| 125 | def verify_rest(self): |
| 126 | self.get('/api/v1') |
| 127 | |
| 128 | # Create a new simulated device |
| 129 | def add_device(self): |
| 130 | device = Device( |
| 131 | type='simulated_olt', |
Richard Jankowski | 8728934 | 2018-04-19 13:59:10 -0400 | [diff] [blame] | 132 | mac_address='00:00:00:00:00:01' |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 133 | ) |
Stephane Barbarie | cd51f99 | 2017-09-07 16:37:02 -0400 | [diff] [blame] | 134 | device = self.post('/api/v1/devices', MessageToDict(device), |
| 135 | expected_http_code=200) |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 136 | return device |
| 137 | |
Stephane Barbarie | cc6b2e6 | 2017-03-02 14:35:55 -0500 | [diff] [blame] | 138 | # Active the simulated device. |
| 139 | # This will trigger the simulation of random alarms |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 140 | def activate_device(self, device_id): |
Stephane Barbarie | cd51f99 | 2017-09-07 16:37:02 -0400 | [diff] [blame] | 141 | path = '/api/v1/devices/{}'.format(device_id) |
| 142 | self.post(path + '/enable', expected_http_code=200) |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 143 | device = self.get(path) |
| 144 | self.assertEqual(device['admin_state'], 'ENABLED') |
| 145 | |
| 146 | # Retrieve a sample alarm for a specific device |
| 147 | def get_alarm_event(self, device_id): |
| 148 | cmd = COMMANDS['kafka_client_alarm_check'].format(self.kafka_endpoint) |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 149 | kafka_client_output = run_long_running_command_with_timeout(cmd, 30) |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 150 | |
| 151 | # Verify the kafka client output |
| 152 | found = False |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 153 | alarm_data = None |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 154 | |
| 155 | for out in kafka_client_output: |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 156 | # Catch any error that might occur while reading the kafka messages |
| 157 | try: |
| 158 | alarm_data = simplejson.loads(out) |
| 159 | print alarm_data |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 160 | |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 161 | if not alarm_data or 'resource_id' not in alarm_data: |
| 162 | continue |
| 163 | elif alarm_data['resource_id'] == device_id: |
| 164 | found = True |
| 165 | break |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 166 | |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 167 | except Exception as e: |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 168 | continue |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 169 | |
Stephane Barbarie | cc6b2e6 | 2017-03-02 14:35:55 -0500 | [diff] [blame] | 170 | self.assertTrue( |
| 171 | found, |
| 172 | 'Failed to find kafka alarm with device id:{}'.format(device_id)) |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 173 | |
Stephane Barbarie | e37300e | 2017-06-08 11:22:16 -0400 | [diff] [blame] | 174 | return alarm_data |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 175 | |
| 176 | # Verify that the alarm follows the proper schema structure |
| 177 | def validate_alarm_event_schema(self, alarm): |
| 178 | try: |
| 179 | jsonschema.validate(alarm, ALARM_SCHEMA) |
| 180 | except Exception as e: |
Stephane Barbarie | cc6b2e6 | 2017-03-02 14:35:55 -0500 | [diff] [blame] | 181 | self.assertTrue( |
| 182 | False, 'Validation failed for alarm : {}'.format(e.message)) |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 183 | |
| 184 | # Verify that alarm identifier based on the format generated by default. |
| 185 | def verify_alarm_event_id(self, device_id, alarm_id): |
| 186 | prefix = re.findall(r"(voltha)\.(\w+)\.(\w+)", alarm_id) |
| 187 | |
Stephane Barbarie | cc6b2e6 | 2017-03-02 14:35:55 -0500 | [diff] [blame] | 188 | self.assertEqual( |
| 189 | len(prefix), 1, |
| 190 | 'Failed to parse the alarm id: {}'.format(alarm_id)) |
| 191 | self.assertEqual( |
| 192 | len(prefix[0]), 3, |
| 193 | 'Expected id format: voltha.<adapter name>.<device id>') |
| 194 | self.assertEqual( |
| 195 | prefix[0][0], 'voltha', |
| 196 | 'Expected id format: voltha.<adapter name>.<device id>') |
| 197 | self.assertEqual( |
| 198 | prefix[0][1], 'simulated_olt', |
| 199 | 'Expected id format: voltha.<adapter name>.<device id>') |
| 200 | self.assertEqual( |
| 201 | prefix[0][2], device_id, |
| 202 | 'Expected id format: voltha.<adapter name>.<device id>') |
Stephane Barbarie | 52198b9 | 2017-03-02 13:44:46 -0500 | [diff] [blame] | 203 | |
| 204 | |
| 205 | if __name__ == '__main__': |
| 206 | main() |