| # Copyright 2019 Google LLC |
| # |
| # 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. |
| |
| """Tests cluster_api module.""" |
| |
| import datetime |
| |
| from protorpc import protojson |
| from tradefed_cluster import api_messages |
| from tradefed_cluster import api_test |
| from tradefed_cluster import cluster_api |
| from tradefed_cluster import datastore_entities |
| from tradefed_cluster import datastore_test_util |
| |
| import unittest |
| |
| |
| class ClusterApiTest(api_test.ApiTest): |
| |
| TIMESTAMP = datetime.datetime.utcfromtimestamp(1431712965) |
| |
| def setUp(self): |
| api_test.ApiTest.setUp(self) |
| self.host_update_state_summary_0 = ( |
| datastore_entities.HostUpdateStateSummary( |
| total=2, |
| succeeded=2, |
| target_version='v1')) |
| host_count_by_harness_version = { |
| 'version1': 72, |
| 'version2': 20, |
| } |
| self.cluster_0 = datastore_test_util.CreateCluster( |
| cluster='free', |
| total_devices=10, |
| offline_devices=1, |
| available_devices=2, |
| allocated_devices=7, |
| device_count_timestamp=self.TIMESTAMP, |
| host_update_state_summary=self.host_update_state_summary_0, |
| host_update_state_summaries_by_version=[ |
| self.host_update_state_summary_0], |
| host_count_by_harness_version=host_count_by_harness_version) |
| self.cluster_1 = datastore_test_util.CreateCluster( |
| cluster='paid', |
| total_devices=2, |
| offline_devices=0, |
| available_devices=1, |
| allocated_devices=1, |
| device_count_timestamp=self.TIMESTAMP) |
| datastore_test_util.CreateHost(cluster='free', hostname='host_0') |
| datastore_test_util.CreateDevice( |
| cluster='free', |
| hostname='host_0', |
| device_serial='device_0', |
| device_type=api_messages.DeviceTypeMessage.PHYSICAL, |
| battery_level='100', |
| run_target='shamu') |
| datastore_test_util.CreateHost(cluster='free', hostname='host_1') |
| datastore_test_util.CreateDevice( |
| cluster='free', |
| hostname='host_1', |
| device_serial='device_1', |
| device_type=api_messages.DeviceTypeMessage.PHYSICAL, |
| timestamp=self.TIMESTAMP, |
| run_target='flounder') |
| |
| datastore_test_util.CreateHost(cluster='paid', hostname='host_2') |
| datastore_test_util.CreateDevice( |
| cluster='paid', |
| hostname='host_2', |
| device_serial='device_2', |
| device_type=api_messages.DeviceTypeMessage.PHYSICAL, |
| run_target='shamu') |
| # A hidden device |
| datastore_test_util.CreateDevice( |
| cluster='paid', |
| hostname='host_2', |
| device_serial='device_3', |
| device_type=api_messages.DeviceTypeMessage.PHYSICAL, |
| run_target='shamu', |
| hidden=True) |
| # A hidden host |
| datastore_test_util.CreateHost( |
| cluster='paid', hostname='host_3', hidden=True) |
| |
| self.note = datastore_entities.Note(user='user0', |
| timestamp=self.TIMESTAMP, |
| message='Hello, World') |
| cluster_note = datastore_entities.ClusterNote(cluster='free') |
| cluster_note.note = self.note |
| cluster_note.put() |
| |
| def AssertEqualClusterInfo(self, cluster_entity, cluster_message): |
| # Helper to compare cluster entities and messages |
| self.assertEqual(cluster_entity.cluster, cluster_message.cluster_id) |
| self.assertEqual( |
| cluster_entity.total_devices, cluster_message.total_devices) |
| self.assertEqual( |
| cluster_entity.offline_devices, cluster_message.offline_devices) |
| self.assertEqual( |
| cluster_entity.available_devices, cluster_message.available_devices) |
| self.assertEqual( |
| cluster_entity.allocated_devices, cluster_message.allocated_devices) |
| self.assertEqual( |
| cluster_entity.device_count_timestamp, |
| cluster_message.device_count_timestamp) |
| |
| def testListClusters(self): |
| """Tests ListClusters.""" |
| api_request = {} |
| api_response = self.testapp.post_json( |
| '/_ah/api/ClusterApi.ListClusters', api_request) |
| cluster_collection = protojson.decode_message( |
| cluster_api.ClusterInfoCollection, api_response.body) |
| self.assertEqual('200 OK', api_response.status) |
| clusters = [c for c in cluster_collection.cluster_infos] |
| for c in clusters: |
| self.assertEqual(0, len(c.host_infos)) |
| self.assertEqual(0, len(c.run_targets)) |
| if c.cluster_id == 'free': |
| self.AssertEqualClusterInfo(self.cluster_0, c) |
| elif c.cluster_id == 'paid': |
| self.AssertEqualClusterInfo(self.cluster_1, c) |
| else: |
| # No other cluster should exist |
| self.fail() |
| |
| def testListClusters_includeHosts(self): |
| """Tests ListClusters returns all visible clusters, hosts, and devices.""" |
| api_request = {'include_hosts': True} |
| api_response = self.testapp.post_json( |
| '/_ah/api/ClusterApi.ListClusters', api_request) |
| cluster_collection = protojson.decode_message( |
| cluster_api.ClusterInfoCollection, api_response.body) |
| self.assertEqual('200 OK', api_response.status) |
| clusters = [c for c in cluster_collection.cluster_infos] |
| hosts = [] |
| for c in clusters: |
| if c.cluster_id == 'free': |
| self.AssertEqualClusterInfo(self.cluster_0, c) |
| self.assertItemsEqual( |
| ('shamu', 'flounder'), (rt.name for rt in c.run_targets)) |
| elif c.cluster_id == 'paid': |
| self.AssertEqualClusterInfo(self.cluster_1, c) |
| self.assertEqual('shamu', c.run_targets[0].name) |
| else: |
| # No other cluster should exist |
| self.fail() |
| for host in c.host_infos: |
| hosts.append(host) |
| devices = [] |
| for host in hosts: |
| for device in host.device_infos: |
| devices.append(device) |
| self.assertEqual(2, len(clusters)) |
| self.assertEqual(3, len(hosts)) |
| self.assertEqual(3, len(devices)) |
| self.assertItemsEqual(['free', 'paid'], |
| [c.cluster_id for c in clusters]) |
| self.assertItemsEqual(['host_0', 'host_1', 'host_2'], |
| [h.hostname for h in hosts]) |
| self.assertItemsEqual(['device_0', 'device_1', 'device_2'], |
| [d.device_serial for d in devices]) |
| |
| def testGetCluster(self): |
| """Tests GetCluster returns hosts in order with their devices.""" |
| api_request = {'cluster_id': 'free'} |
| api_response = self.testapp.post_json( |
| '/_ah/api/ClusterApi.GetCluster', api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| self.assertEqual('200 OK', api_response.status) |
| self.assertEqual('free', cluster_info.cluster_id) |
| self.assertEqual(0, len(cluster_info.host_infos)) |
| |
| def testGetCluster_includeHosts(self): |
| """Tests GetCluster returns hosts in order with their devices.""" |
| api_request = {'cluster_id': 'free', 'include_hosts': True} |
| api_response = self.testapp.post_json( |
| '/_ah/api/ClusterApi.GetCluster', api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| self.assertEqual('200 OK', api_response.status) |
| self.assertEqual('free', cluster_info.cluster_id) |
| self.assertEqual(2, len(cluster_info.host_infos)) |
| self.assertEqual('host_0', cluster_info.host_infos[0].hostname) |
| self.assertEqual(1, len(cluster_info.host_infos[0].device_infos)) |
| self.assertEqual('device_0', |
| cluster_info.host_infos[0].device_infos[0].device_serial) |
| self.assertEqual('100', |
| cluster_info.host_infos[0].device_infos[0].battery_level) |
| self.assertIsNone(cluster_info.host_infos[0].device_infos[0].timestamp) |
| self.assertEqual('host_1', cluster_info.host_infos[1].hostname) |
| self.assertEqual(1, len(cluster_info.host_infos[1].device_infos)) |
| self.assertEqual('device_1', |
| cluster_info.host_infos[1].device_infos[0].device_serial) |
| self.assertEqual(self.TIMESTAMP, |
| cluster_info.host_infos[1].device_infos[0].timestamp) |
| self.assertEqual(0, len(cluster_info.notes)) |
| self.assertItemsEqual(['shamu', 'flounder'], |
| [r.name for r in cluster_info.run_targets]) |
| |
| def testGetCluster_missingCluster(self): |
| """Tests GetCluster for an inexistent one.""" |
| api_request = {'cluster_id': 'fakecluster'} |
| api_response = self.testapp.post_json( |
| '/_ah/api/ClusterApi.GetCluster', api_request, expect_errors=True) |
| self.assertEqual('404 Not Found', api_response.status) |
| |
| def testGetCluster_includeNotes(self): |
| """Tests GetCluster returns hosts in order with their devices.""" |
| api_request = {'cluster_id': 'free', 'include_notes': True} |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.GetCluster', |
| api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| self.assertEqual('200 OK', api_response.status) |
| self.assertEqual('free', cluster_info.cluster_id) |
| self.assertEqual(1, len(cluster_info.notes)) |
| self.assertEqual(self.note.user, cluster_info.notes[0].user) |
| self.assertEqual(self.note.timestamp, cluster_info.notes[0].timestamp) |
| self.assertEqual(self.note.message, cluster_info.notes[0].message) |
| |
| def testGetCluster_includeNotesNoneAvailable(self): |
| """Tests GetCluster including notes when they are available.""" |
| api_request = {'cluster_id': 'paid', 'include_notes': True} |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.GetCluster', |
| api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| self.assertEqual('200 OK', api_response.status) |
| self.assertEqual('paid', cluster_info.cluster_id) |
| self.assertEqual(0, len(cluster_info.notes)) |
| |
| def testGetCluster_withHostUpdateStateSummary(self): |
| """Tests GetCluster where the cluster has host update state summary.""" |
| api_request = {'cluster_id': 'free'} |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.GetCluster', |
| api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| host_update_state_summary = cluster_info.host_update_state_summary |
| self.assertEqual('200 OK', api_response.status) |
| self.assertEqual('free', cluster_info.cluster_id) |
| self.assertEqual(2, host_update_state_summary.total) |
| self.assertEqual(2, host_update_state_summary.succeeded) |
| self.assertEqual(0, host_update_state_summary.pending) |
| self.assertEqual(0, host_update_state_summary.syncing) |
| self.assertEqual(0, host_update_state_summary.shutting_down) |
| self.assertEqual(0, host_update_state_summary.restarting) |
| self.assertEqual(0, host_update_state_summary.timed_out) |
| self.assertEqual(0, host_update_state_summary.errored) |
| self.assertEqual(0, host_update_state_summary.unknown) |
| self.assertIsNotNone(host_update_state_summary.update_timestamp) |
| |
| def testGetCluster_withHostUpdateStateSummaryPerVersion(self): |
| """Tests GetCluster where the cluster has host update state summary.""" |
| api_request = {'cluster_id': 'free'} |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.GetCluster', |
| api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| summaries = cluster_info.host_update_state_summaries_by_version |
| self.assertEqual('200 OK', api_response.status) |
| self.assertEqual('free', cluster_info.cluster_id) |
| self.assertEqual(2, summaries[0].total) |
| self.assertEqual(2, summaries[0].succeeded) |
| self.assertEqual('v1', summaries[0].target_version) |
| |
| def testGetCluster_withHostCountByHarnessVersion(self): |
| """Tests GetCluster where the cluster has host count by harness version.""" |
| api_request = {'cluster_id': 'free'} |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.GetCluster', |
| api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| expected_host_counts = [ |
| api_messages.KeyValuePair(key='version1', value='72'), |
| api_messages.KeyValuePair(key='version2', value='20'), |
| ] |
| self.assertCountEqual( |
| expected_host_counts, cluster_info.host_count_by_harness_version) |
| |
| def testNewNote_withNoneExisting(self): |
| """Tests adding a note to a cluster when none exist already.""" |
| user = 'some_user' |
| timestamp = datetime.datetime(2015, 10, 18, 20, 46) |
| message = 'The Message' |
| api_request = {'cluster_id': 'paid', |
| 'user': user, |
| 'timestamp': timestamp.isoformat(), |
| 'message': message |
| } |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.NewNote', |
| api_request) |
| self.assertEqual('200 OK', api_response.status) |
| api_request = {'cluster_id': 'paid', 'include_notes': True} |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.GetCluster', |
| api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| self.assertEqual(1, len(cluster_info.notes)) |
| self.assertEqual(user, cluster_info.notes[0].user) |
| self.assertEqual(timestamp, cluster_info.notes[0].timestamp) |
| self.assertEqual(message, cluster_info.notes[0].message) |
| |
| def testNewNote_withExisting(self): |
| """Tests adding a note to a cluster when one already exists.""" |
| user = 'some_user' |
| timestamp = datetime.datetime(2015, 10, 18, 20, 46) |
| message = 'The Message' |
| api_request = {'cluster_id': 'free', |
| 'user': user, |
| 'timestamp': timestamp.isoformat(), |
| 'message': message |
| } |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.NewNote', |
| api_request) |
| self.assertEqual('200 OK', api_response.status) |
| # Query the same cluster again. Notes should be sorted. |
| api_request = {'cluster_id': 'free', 'include_notes': True} |
| api_response = self.testapp.post_json('/_ah/api/ClusterApi.GetCluster', |
| api_request) |
| cluster_info = protojson.decode_message(api_messages.ClusterInfo, |
| api_response.body) |
| self.assertEqual(2, len(cluster_info.notes)) |
| self.assertEqual(user, cluster_info.notes[0].user) |
| self.assertEqual(timestamp, cluster_info.notes[0].timestamp) |
| self.assertEqual(message, cluster_info.notes[0].message) |
| self.assertEqual(self.note.user, cluster_info.notes[1].user) |
| self.assertEqual(self.note.timestamp, cluster_info.notes[1].timestamp) |
| self.assertEqual(self.note.message, cluster_info.notes[1].message) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |