"Fossies" - the Fresh Open Source Software Archive

Member "masakari-9.0.0/masakari/tests/unit/api/openstack/ha/test_notifications.py" (13 May 2020, 21557 Bytes) of package /linux/misc/openstack/masakari-9.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "test_notifications.py": 8.0.0_vs_9.0.0.

    1 # Copyright (c) 2016 NTT DATA
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 
   16 """Tests for the notifications api."""
   17 
   18 import copy
   19 from unittest import mock
   20 
   21 import ddt
   22 from oslo_serialization import jsonutils
   23 from oslo_utils import timeutils
   24 from six.moves import http_client as http
   25 from webob import exc
   26 
   27 from masakari.api.openstack.ha import notifications
   28 from masakari.engine import rpcapi as engine_rpcapi
   29 from masakari import exception
   30 from masakari.ha import api as ha_api
   31 from masakari.objects import base as obj_base
   32 from masakari.objects import fields
   33 from masakari.objects import notification as notification_obj
   34 from masakari import test
   35 from masakari.tests.unit.api.openstack import fakes
   36 from masakari.tests.unit.objects import test_objects
   37 from masakari.tests import uuidsentinel
   38 
   39 NOW = timeutils.utcnow().replace(microsecond=0)
   40 OPTIONAL = ['recovery_workflow_details']
   41 
   42 
   43 def _make_notification_obj(notification_dict):
   44     return notification_obj.Notification(**notification_dict)
   45 
   46 
   47 def _make_notification_progress_details_obj(progress_details):
   48     return notification_obj.NotificationProgressDetails(**progress_details)
   49 
   50 
   51 def _make_notifications_list(notifications_list):
   52     return notification_obj.Notification(objects=[
   53         _make_notification_obj(a) for a in notifications_list])
   54 
   55 NOTIFICATION_DATA = {"type": "VM", "id": 1,
   56                      "payload":
   57                          {'event': 'STOPPED', 'host_status': 'NORMAL',
   58                           'cluster_status': 'ONLINE'},
   59                      "source_host_uuid": uuidsentinel.fake_host,
   60                      "generated_time": NOW,
   61                      "status": "running",
   62                      "notification_uuid": uuidsentinel.fake_notification,
   63                      "created_at": NOW,
   64                      "updated_at": None,
   65                      "deleted_at": None,
   66                      "deleted": 0
   67                      }
   68 
   69 NOTIFICATION = _make_notification_obj(NOTIFICATION_DATA)
   70 
   71 RECOVERY_DETAILS = {"progress": 1.0,
   72                     "state": "SUCCESS",
   73                     "name": "StopInstanceTask",
   74                     "progress_details": [
   75                         {"timestamp": "2019-03-07 13:54:28",
   76                          "message": "Stopping instance",
   77                          "progress": "0.0"},
   78                     ]}
   79 
   80 NOTI_DATA_WITH_DETAILS = copy.deepcopy(NOTIFICATION_DATA)
   81 
   82 NOTIFICATION_WITH_PROGRESS_DETAILS = _make_notification_obj(
   83     NOTI_DATA_WITH_DETAILS)
   84 RECOVERY_OBJ = _make_notification_progress_details_obj(RECOVERY_DETAILS)
   85 NOTIFICATION_WITH_PROGRESS_DETAILS.recovery_workflow_details = [RECOVERY_OBJ]
   86 
   87 NOTIFICATION_LIST = [
   88     {"type": "VM", "id": 1, "payload": {'event': 'STOPPED',
   89                                         'host_status': 'NORMAL',
   90                                         'cluster_status': 'ONLINE'},
   91      "source_host_uuid": uuidsentinel.fake_host, "generated_time": NOW,
   92      "status": "running", "notification_uuid": uuidsentinel.fake_notification,
   93      "created_at": NOW, "updated_at": None, "deleted_at": None, "deleted": 0},
   94 
   95     {"type": "PROCESS", "id": 2, "payload": {'event': 'STOPPED',
   96                                              'process_name': 'fake_process'},
   97      "source_host_uuid": uuidsentinel.fake_host1, "generated_time": NOW,
   98      "status": "running", "notification_uuid": uuidsentinel.fake_notification1,
   99      "created_at": NOW, "updated_at": None, "deleted_at": None, "deleted": 0},
  100 ]
  101 
  102 NOTIFICATION_LIST = _make_notifications_list(NOTIFICATION_LIST)
  103 
  104 
  105 @ddt.ddt
  106 class NotificationTestCase(test.TestCase):
  107     """Test Case for notifications api."""
  108 
  109     bad_request = exception.ValidationError
  110 
  111     @mock.patch.object(engine_rpcapi, 'EngineAPI')
  112     def setUp(self, mock_rpc):
  113         super(NotificationTestCase, self).setUp()
  114         self.controller = notifications.NotificationsController()
  115         self.req = fakes.HTTPRequest.blank('/v1/notifications',
  116                                            use_admin_context=True)
  117         self.context = self.req.environ['masakari.context']
  118 
  119     @property
  120     def app(self):
  121         return fakes.wsgi_app_v1(init_only='os-hosts')
  122 
  123     def _assert_notification_data(self, expected, actual):
  124         self.assertTrue(obj_base.obj_equal_prims(expected, actual),
  125                         "The notifications objects were not equal")
  126 
  127     @mock.patch.object(ha_api.NotificationAPI, 'get_all')
  128     def test_index(self, mock_get_all):
  129 
  130         mock_get_all.return_value = NOTIFICATION_LIST
  131 
  132         result = self.controller.index(self.req)
  133         result = result['notifications']
  134         self._assert_notification_data(NOTIFICATION_LIST,
  135                                        _make_notifications_list(result))
  136 
  137     @ddt.data(
  138         # limit negative
  139         "limit=-1",
  140 
  141         # invalid sort key
  142         "sort_key=abcd",
  143 
  144         # invalid sort dir
  145         "sort_dir=abcd")
  146     def test_index_invalid(self, param):
  147         req = fakes.HTTPRequest.blank("/v1/notifications?%s" % param,
  148                                       use_admin_context=True)
  149 
  150         self.assertRaises(exc.HTTPBadRequest, self.controller.index, req)
  151 
  152     @mock.patch.object(ha_api.NotificationAPI, 'get_all')
  153     def test_index_marker_not_found(self, mock_get_all):
  154         fake_request = fakes.HTTPRequest.blank('/v1/notifications?marker=1234',
  155                                                use_admin_context=True)
  156         mock_get_all.side_effect = exception.MarkerNotFound(marker="1234")
  157         self.assertRaises(exc.HTTPBadRequest, self.controller.index,
  158                           fake_request)
  159 
  160     def test_index_invalid_generated_since(self):
  161 
  162         req = fakes.HTTPRequest.blank('/v1/notifications?generated-since=abcd',
  163                                       use_admin_context=True)
  164         self.assertRaises(exc.HTTPBadRequest, self.controller.index, req)
  165 
  166     @mock.patch.object(ha_api.NotificationAPI, 'get_all')
  167     def test_index_valid_generated_since(self, mock_get_all):
  168         url = '/v1/notifications?generated-since=%s' % str(NOW)
  169         req = fakes.HTTPRequest.blank(url, use_admin_context=True)
  170         mock_get_all.return_value = NOTIFICATION_LIST
  171         result = self.controller.index(req)
  172         result = result['notifications']
  173         self._assert_notification_data(NOTIFICATION_LIST,
  174                                        _make_notifications_list(result))
  175 
  176     @mock.patch.object(ha_api.NotificationAPI, 'create_notification')
  177     def test_create(self, mock_create):
  178 
  179         mock_create.return_value = NOTIFICATION
  180         result = self.controller.create(self.req, body={
  181             "notification": {
  182                 "hostname": "fake_host",
  183                 "payload": {
  184                     "instance_uuid": uuidsentinel.instance_uuid,
  185                     "vir_domain_event": "STOPPED_FAILED",
  186                     "event": "LIFECYCLE"
  187                 },
  188                 "type": "VM",
  189                 "generated_time": "2016-09-13T09:11:21.656788"}})
  190         result = result['notification']
  191         test_objects.compare_obj(self, result, NOTIFICATION_DATA,
  192                                  allow_missing=OPTIONAL)
  193 
  194     @mock.patch.object(ha_api.NotificationAPI, 'create_notification')
  195     def test_create_process_notification(self, mock_create):
  196         mock_create.return_value = NOTIFICATION
  197         result = self.controller.create(self.req, body={
  198             "notification": {
  199                 "hostname": "fake_host",
  200                 "payload": {
  201                     "process_name": "nova-compute",
  202                     "event": "STOPPED"
  203                 },
  204                 "type": "PROCESS",
  205                 "generated_time": "2016-09-13T09:11:21.656788"}})
  206         result = result['notification']
  207         test_objects.compare_obj(self, result, NOTIFICATION_DATA,
  208                                  allow_missing=OPTIONAL)
  209 
  210     @mock.patch('masakari.rpc.get_client')
  211     @mock.patch.object(ha_api.NotificationAPI, 'create_notification')
  212     def test_create_success_with_201_response_code(
  213         self, mock_client, mock_create):
  214         body = {
  215             "notification": {
  216                 "hostname": "fake_host",
  217                 "payload": {
  218                     "instance_uuid": uuidsentinel.instance_uuid,
  219                     "vir_domain_event": "STOPPED_FAILED",
  220                     "event": "LIFECYCLE"
  221                 },
  222                 "type": "VM",
  223                 "generated_time": NOW
  224             }
  225         }
  226         fake_req = self.req
  227         fake_req.headers['Content-Type'] = 'application/json'
  228         fake_req.method = 'POST'
  229         fake_req.body = jsonutils.dump_as_bytes(body)
  230         resp = fake_req.get_response(self.app)
  231         self.assertEqual(http.ACCEPTED, resp.status_code)
  232 
  233     @mock.patch.object(ha_api.NotificationAPI, 'create_notification')
  234     def test_create_host_not_found(self, mock_create):
  235         body = {
  236             "notification": {
  237                 "hostname": "fake_host",
  238                 "payload": {
  239                     "instance_uuid": uuidsentinel.instance_uuid,
  240                     "vir_domain_event": "STOPPED_FAILED",
  241                     "event": "LIFECYCLE"
  242                 },
  243                 "type": "VM",
  244                 "generated_time": "2016-09-13T09:11:21.656788"
  245             }
  246         }
  247         mock_create.side_effect = exception.HostNotFoundByName(
  248             host_name="fake_host")
  249         self.assertRaises(exc.HTTPBadRequest, self.controller.create,
  250                           self.req, body=body)
  251 
  252     @ddt.data(
  253         # invalid type
  254         {"body": {
  255             "notification": {"hostname": "fake_host",
  256                              "payload": {"event": "STOPPED",
  257                                          "host_status": "NORMAL",
  258                                          "cluster_status": "ONLINE"},
  259                              "type": "Fake",
  260                              "generated_time": "2016-09-13T09:11:21.656788"}}},
  261 
  262         # no notification in body
  263         {"body": {"hostname": "fake_host",
  264                   "payload": {"event": "STOPPED",
  265                               "host_status": "NORMAL",
  266                               "cluster_status": "ONLINE"},
  267                   "type": "VM",
  268                   "generated_time": "2016-09-13T09:11:21.656788"}},
  269 
  270         # no payload
  271         {"body": {"notification": {"hostname": "fake_host",
  272                                    "type": "VM",
  273                                    "generated_time":
  274                                        "2016-09-13T09:11:21.656788"}}},
  275 
  276         # no hostname
  277         {"body": {"notification": {"payload": {"event": "STOPPED",
  278                                                "host_status": "NORMAL",
  279                                                "cluster_status": "ONLINE"},
  280                                    "type": "VM",
  281                                    "generated_time":
  282                                        "2016-09-13T09:11:21.656788"}}},
  283 
  284         # no type
  285         {"body": {"notification": {"hostname": "fake_host",
  286                                    "payload": {"event": "STOPPED",
  287                                                "host_status": "NORMAL",
  288                                                "cluster_status": "ONLINE"},
  289                                    "generated_time":
  290                                        "2016-09-13T09:11:21.656788"}}},
  291 
  292         # no generated time
  293         {"body": {"notification": {"hostname": "fake_host",
  294                                    "payload": {"event": "STOPPED",
  295                                                "host_status": "NORMAL",
  296                                                "cluster_status": "ONLINE"},
  297                                    "type": "VM",
  298                                    }}},
  299 
  300         # hostname too long
  301         {"body": {
  302             "notification": {"hostname": "fake_host" * 255,
  303                              "payload": {"event": "STOPPED",
  304                                          "host_status": "NORMAL",
  305                                          "cluster_status": "ONLINE"},
  306                              "type": "VM",
  307                              "generated_time": "2016-09-13T09:11:21.656788"}}},
  308 
  309         # extra invalid args
  310         {"body": {
  311             "notification": {"hostname": "fake_host",
  312                              "payload": {"event": "STOPPED",
  313                                          "host_status": "NORMAL",
  314                                          "cluster_status": "ONLINE"},
  315                              "type": "VM",
  316                              "generated_time": "2016-09-13T09:11:21.656788",
  317                              "invalid_extra": "non_expected_parameter"}}}
  318     )
  319     @ddt.unpack
  320     def test_create_failure(self, body):
  321         self.assertRaises(self.bad_request, self.controller.create,
  322                           self.req, body=body)
  323 
  324     @ddt.data(
  325         # invalid event for PROCESS type
  326         {"params": {"payload": {"event": "invalid",
  327                                 "process_name": "nova-compute"},
  328                     "type": fields.NotificationType.PROCESS}},
  329 
  330         # invalid event for VM type
  331         {"params": {"payload": {"event": "invalid",
  332                                 "host_status": fields.HostStatusType.NORMAL,
  333                           "cluster_status": fields.ClusterStatusType.ONLINE},
  334                     "type": fields.NotificationType.VM}},
  335 
  336         # invalid event for HOST_COMPUTE type
  337         {"params": {"payload": {"event": "invalid"},
  338                     "type": fields.NotificationType.COMPUTE_HOST}},
  339 
  340         # empty payload
  341         {"params": {"payload": {},
  342                     "type": fields.NotificationType.COMPUTE_HOST}},
  343 
  344         # empty process_name
  345         {"params": {"payload": {"event": fields.EventType.STOPPED,
  346                                 "process_name": ""},
  347                     "type": fields.NotificationType.PROCESS}},
  348 
  349         # process_name too long value
  350         {"params": {"payload": {"event": fields.EventType.STOPPED,
  351                               "process_name": "a" * 4097},
  352                   "type": fields.NotificationType.PROCESS}},
  353 
  354         # process_name invalid data_type
  355         {"params": {"payload": {"event": fields.EventType.STOPPED,
  356                               "process_name": 123},
  357                   "type": fields.NotificationType.PROCESS}}
  358     )
  359     @ddt.unpack
  360     def test_create_with_invalid_payload(self, params):
  361         body = {
  362             "notification": {"hostname": "fake_host",
  363                              "generated_time": "2016-09-13T09:11:21.656788"
  364                              }
  365         }
  366 
  367         body['notification']['payload'] = params['payload']
  368         body['notification']['type'] = params['type']
  369         self.assertRaises(self.bad_request, self.controller.create,
  370                           self.req, body=body)
  371 
  372     @mock.patch.object(ha_api.NotificationAPI, 'create_notification')
  373     def test_create_duplicate_notification(self, mock_create_notification):
  374         mock_create_notification.side_effect = exception.DuplicateNotification(
  375             type="COMPUTE_HOST")
  376         body = {
  377             "notification": {"hostname": "fake_host",
  378                              "payload": {"event": "STOPPED",
  379                                          "host_status": "NORMAL",
  380                                          "cluster_status": "ONLINE"},
  381                              "type": "COMPUTE_HOST",
  382                              "generated_time": str(NOW)}}
  383         self.assertRaises(exc.HTTPConflict, self.controller.create,
  384                           self.req, body=body)
  385 
  386     @mock.patch.object(ha_api.NotificationAPI, 'create_notification')
  387     def test_create_host_on_maintenance(self, mock_create_notification):
  388         mock_create_notification.side_effect = (
  389             exception.HostOnMaintenanceError(host_name="fake_host"))
  390         body = {
  391             "notification": {"hostname": "fake_host",
  392                              "payload": {"event": "STOPPED",
  393                                          "host_status": "NORMAL",
  394                                          "cluster_status": "ONLINE"},
  395                              "type": "COMPUTE_HOST",
  396                              "generated_time": str(NOW)}}
  397         self.assertRaises(exc.HTTPConflict, self.controller.create,
  398                           self.req, body=body)
  399 
  400     @mock.patch.object(ha_api.NotificationAPI, 'get_notification')
  401     def test_show(self, mock_get_notification):
  402 
  403         mock_get_notification.return_value = NOTIFICATION
  404 
  405         result = self.controller.show(self.req, uuidsentinel.fake_notification)
  406         result = result['notification']
  407         self._assert_notification_data(NOTIFICATION,
  408                                        _make_notification_obj(result))
  409 
  410     @mock.patch.object(ha_api.NotificationAPI, 'get_notification')
  411     def test_show_with_non_existing_uuid(self, mock_get_notification):
  412 
  413         mock_get_notification.side_effect = exception.NotificationNotFound(
  414             id="2")
  415         self.assertRaises(exc.HTTPNotFound,
  416                           self.controller.show, self.req, "2")
  417 
  418     @ddt.data('DELETE', 'PUT')
  419     @mock.patch('masakari.rpc.get_client')
  420     def test_delete_and_update_notification(self, method, mock_client):
  421         url = '/v1/notifications/%s' % uuidsentinel.fake_notification
  422         fake_req = fakes.HTTPRequest.blank(url, use_admin_context=True)
  423         fake_req.headers['Content-Type'] = 'application/json'
  424         fake_req.method = method
  425         resp = fake_req.get_response(self.app)
  426         self.assertEqual(http.METHOD_NOT_ALLOWED, resp.status_code)
  427 
  428 
  429 class NotificationCasePolicyNotAuthorized(test.NoDBTestCase):
  430     """Test Case for notifications non admin."""
  431 
  432     @mock.patch.object(engine_rpcapi, 'EngineAPI')
  433     def setUp(self, mock_rpc):
  434         super(NotificationCasePolicyNotAuthorized, self).setUp()
  435         self.controller = notifications.NotificationsController()
  436         self.req = fakes.HTTPRequest.blank('/v1/notifications')
  437         self.context = self.req.environ['masakari.context']
  438 
  439     def _check_rule(self, exc, rule_name):
  440         self.assertEqual(
  441             "Policy doesn't allow %s to be performed." % rule_name,
  442             exc.format_message())
  443 
  444     def test_create_no_admin(self):
  445         rule_name = "os_masakari_api:notifications:create"
  446         self.policy.set_rules({rule_name: "project:non_fake"})
  447         body = {
  448             "notification": {"hostname": "fake_host",
  449                              "payload": {"event": "STOPPED",
  450                                          "host_status": "NORMAL",
  451                                          "cluster_status": "ONLINE"},
  452                              "type": "VM",
  453                              "generated_time": "2016-09-13T09:11:21.656788"}}
  454         exc = self.assertRaises(exception.PolicyNotAuthorized,
  455                                 self.controller.create,
  456                                 self.req, body=body)
  457         self._check_rule(exc, rule_name)
  458 
  459     def test_show_no_admin(self):
  460         rule_name = "os_masakari_api:notifications:detail"
  461         self.policy.set_rules({rule_name: "project:non_fake"})
  462         exc = self.assertRaises(exception.PolicyNotAuthorized,
  463                                 self.controller.show,
  464                                 self.req, uuidsentinel.fake_notification)
  465         self._check_rule(exc, rule_name)
  466 
  467     def test_index_no_admin(self):
  468         rule_name = "os_masakari_api:notifications:index"
  469         self.policy.set_rules({rule_name: "project:non_fake"})
  470         exc = self.assertRaises(exception.PolicyNotAuthorized,
  471                                 self.controller.index,
  472                                 self.req)
  473         self._check_rule(exc, rule_name)
  474 
  475 
  476 class NotificationV1_1_TestCase(NotificationTestCase):
  477     """Test Case for notifications api for 1.1 API"""
  478     api_version = '1.1'
  479 
  480     @mock.patch.object(engine_rpcapi, 'EngineAPI')
  481     def setUp(self, mock_rpc):
  482         super(NotificationV1_1_TestCase, self).setUp()
  483         self.controller = notifications.NotificationsController()
  484         self.req = fakes.HTTPRequest.blank('/v1/notifications',
  485                                            use_admin_context=True,
  486                                            version=self.api_version)
  487         self.context = self.req.environ['masakari.context']
  488 
  489     @mock.patch.object(ha_api.NotificationAPI,
  490                        'get_notification_recovery_workflow_details')
  491     def test_show(self, mock_get_notification_recovery_workflow_details):
  492         (mock_get_notification_recovery_workflow_details
  493          .return_value) = NOTIFICATION_WITH_PROGRESS_DETAILS
  494 
  495         result = self.controller.show(self.req, uuidsentinel.fake_notification)
  496         result = result['notification']
  497         self.assertItemsEqual([RECOVERY_OBJ],
  498                               result.recovery_workflow_details)
  499         self._assert_notification_data(NOTIFICATION_WITH_PROGRESS_DETAILS,
  500                                        _make_notification_obj(result))