diff --git a/aodh/api/controllers/v2/alarm_rules/composite.py b/aodh/api/controllers/v2/alarm_rules/composite.py
index 5062a899a..21092760f 100644
--- a/aodh/api/controllers/v2/alarm_rules/composite.py
+++ b/aodh/api/controllers/v2/alarm_rules/composite.py
@@ -41,8 +41,7 @@ class CompositeRule(wtypes.UserType):
     threshold_plugins = None
 
     def __init__(self):
-        threshold_rules = ('threshold',
-                           'gnocchi_resources_threshold',
+        threshold_rules = ('gnocchi_resources_threshold',
                            'gnocchi_aggregation_by_metrics_threshold',
                            'gnocchi_aggregation_by_resources_threshold')
         CompositeRule.threshold_plugins = named.NamedExtensionManager(
diff --git a/aodh/api/controllers/v2/alarm_rules/threshold.py b/aodh/api/controllers/v2/alarm_rules/threshold.py
deleted file mode 100644
index 5c30d28e3..000000000
--- a/aodh/api/controllers/v2/alarm_rules/threshold.py
+++ /dev/null
@@ -1,161 +0,0 @@
-#
-# 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.
-
-from ceilometerclient import client as ceiloclient
-from ceilometerclient import exc as ceiloexc
-import pecan
-import wsme
-from wsme import types as wtypes
-
-from aodh.api.controllers.v2 import base
-from aodh.api.controllers.v2 import utils as v2_utils
-from aodh.i18n import _
-from aodh import keystone_client
-from aodh import storage
-
-
-class AlarmThresholdRule(base.AlarmRule):
-    """Alarm Threshold Rule
-
-    Describe when to trigger the alarm based on computed statistics
-    """
-
-    meter_name = wsme.wsattr(wtypes.text, mandatory=True)
-    "The name of the meter"
-
-    # FIXME(sileht): default doesn't work
-    # workaround: default is set in validate method
-    query = wsme.wsattr([base.Query], default=[])
-    """The query to find the data for computing statistics.
-    Ownership settings are automatically included based on the Alarm owner.
-    """
-
-    period = wsme.wsattr(wtypes.IntegerType(minimum=1), default=60)
-    "The time range in seconds over which query"
-
-    comparison_operator = base.AdvEnum('comparison_operator', str,
-                                       'lt', 'le', 'eq', 'ne', 'ge', 'gt',
-                                       default='eq')
-    "The comparison against the alarm threshold"
-
-    threshold = wsme.wsattr(float, mandatory=True)
-    "The threshold of the alarm"
-
-    statistic = base.AdvEnum('statistic', str, 'max', 'min', 'avg', 'sum',
-                             'count', default='avg')
-    "The statistic to compare to the threshold"
-
-    evaluation_periods = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
-    "The number of historical periods to evaluate the threshold"
-
-    exclude_outliers = wsme.wsattr(bool, default=False)
-    "Whether datapoints with anomalously low sample counts are excluded"
-
-    ceilometer_sample_api_is_supported = None
-
-    def __init__(self, query=None, **kwargs):
-        query = [base.Query(**q) for q in query] if query else []
-        super(AlarmThresholdRule, self).__init__(query=query, **kwargs)
-
-    @classmethod
-    def _check_ceilometer_sample_api(cls):
-        # Check it only once
-        if cls.ceilometer_sample_api_is_supported is None:
-
-            auth_config = pecan.request.cfg.service_credentials
-            client = ceiloclient.get_client(
-                version=2,
-                session=keystone_client.get_session(pecan.request.cfg),
-                # ceiloclient adapter options
-                region_name=auth_config.region_name,
-                interface=auth_config.interface,
-            )
-            try:
-                client.statistics.list(
-                    meter_name="idontthinkthatexistsbutwhatever")
-            except Exception as e:
-                if isinstance(e, ceiloexc.HTTPException):
-                    if e.code == 410:
-                        cls.ceilometer_sample_api_is_supported = False
-                    elif e.code < 500:
-                        cls.ceilometer_sample_api_is_supported = True
-                    else:
-                        raise
-                else:
-                    raise
-            else:
-                # I don't think this meter can exist but how known
-                cls.ceilometer_sample_api_is_supported = True
-
-        if cls.ceilometer_sample_api_is_supported is False:
-            raise base.ClientSideError(
-                "This telemetry installation is not configured to support"
-                "alarm of type 'threshold")
-
-    @staticmethod
-    def validate(threshold_rule):
-        # note(sileht): wsme default doesn't work in some case
-        # workaround for https://bugs.launchpad.net/wsme/+bug/1227039
-        if not threshold_rule.query:
-            threshold_rule.query = []
-
-        # Timestamp is not allowed for AlarmThresholdRule query, as the alarm
-        # evaluator will construct timestamp bounds for the sequence of
-        # statistics queries as the sliding evaluation window advances
-        # over time.
-        v2_utils.validate_query(threshold_rule.query,
-                                storage.SampleFilter.__init__,
-                                allow_timestamps=False)
-        return threshold_rule
-
-    @classmethod
-    def validate_alarm(cls, alarm):
-        cls._check_ceilometer_sample_api()
-        # ensure an implicit constraint on project_id is added to
-        # the query if not already present
-        alarm.threshold_rule.query = v2_utils.sanitize_query(
-            alarm.threshold_rule.query,
-            storage.SampleFilter.__init__,
-            on_behalf_of=alarm.project_id
-        )
-
-    @property
-    def default_description(self):
-        return (_('Alarm when %(meter_name)s is %(comparison_operator)s a '
-                  '%(statistic)s of %(threshold)s over %(period)s seconds') %
-                dict(comparison_operator=self.comparison_operator,
-                     statistic=self.statistic,
-                     threshold=self.threshold,
-                     meter_name=self.meter_name,
-                     period=self.period))
-
-    def as_dict(self):
-        rule = self.as_dict_from_keys(['period', 'comparison_operator',
-                                       'threshold', 'statistic',
-                                       'evaluation_periods', 'meter_name',
-                                       'exclude_outliers'])
-        rule['query'] = [q.as_dict() for q in self.query]
-        return rule
-
-    @classmethod
-    def sample(cls):
-        return cls(meter_name='cpu_util',
-                   period=60,
-                   evaluation_periods=1,
-                   threshold=300.0,
-                   statistic='avg',
-                   comparison_operator='gt',
-                   query=[{'field': 'resource_id',
-                           'value': '2a4d689b-f0b8-49c1-9eef-87cae58d80db',
-                           'op': 'eq',
-                           'type': 'string'}])
diff --git a/aodh/api/controllers/v2/alarms.py b/aodh/api/controllers/v2/alarms.py
index ceb3931d8..60e62eddf 100644
--- a/aodh/api/controllers/v2/alarms.py
+++ b/aodh/api/controllers/v2/alarms.py
@@ -21,10 +21,8 @@
 import datetime
 import itertools
 import json
-import warnings
 
 import croniter
-import debtcollector
 from oslo_config import cfg
 from oslo_log import log
 from oslo_utils import netutils
@@ -273,13 +271,6 @@ class Alarm(base.Base):
 
     @staticmethod
     def validate(alarm):
-        if alarm.type == 'threshold':
-            warnings.simplefilter("always")
-            debtcollector.deprecate(
-                "Ceilometer's API is deprecated as of Ocata. Therefore, "
-                " threshold rule alarms are no longer supported.",
-                version="5.0.0")
-
         Alarm.check_rule(alarm)
         Alarm.check_alarm_actions(alarm)
 
@@ -357,7 +348,7 @@ class Alarm(base.Base):
         return cls(alarm_id=None,
                    name="SwiftObjectAlarm",
                    description="An alarm",
-                   type='threshold',
+                   type='gnocchi_aggregation_by_metrics_threshold',
                    time_constraints=[AlarmTimeConstraint.sample().as_dict()],
                    user_id="c96c887c216949acbdfbd8b494863567",
                    project_id="c96c887c216949acbdfbd8b494863567",
diff --git a/aodh/evaluator/composite.py b/aodh/evaluator/composite.py
index bf1de7750..f7f140078 100644
--- a/aodh/evaluator/composite.py
+++ b/aodh/evaluator/composite.py
@@ -118,7 +118,7 @@ class CompositeEvaluator(evaluator.Evaluator):
     @property
     def threshold_evaluators(self):
         if not self._threshold_evaluators:
-            threshold_types = ('threshold', 'gnocchi_resources_threshold',
+            threshold_types = ('gnocchi_resources_threshold',
                                'gnocchi_aggregation_by_metrics_threshold',
                                'gnocchi_aggregation_by_resources_threshold')
             self._threshold_evaluators = stevedore.NamedExtensionManager(
@@ -151,13 +151,16 @@ class CompositeEvaluator(evaluator.Evaluator):
                          alarm_rule['or'])
                 rules_alarm, rules_ok = zip(*rules)
                 return OrOp(rules_alarm), AndOp(rules_ok)
-        else:
+        elif alarm_rule['type'] in self.threshold_evaluators:
             rule_evaluator = self.threshold_evaluators[alarm_rule['type']].obj
             self.rule_num += 1
             name = self.rule_name_prefix + str(self.rule_num)
             rule = RuleTarget(alarm_rule, rule_evaluator, name)
             self.rule_targets.append(rule)
             return AlarmEvaluation(rule), OkEvaluation(rule)
+        else:
+            LOG.error("Invalid rule type: %s" % alarm_rule['type'])
+            return False, False
 
     def _reason(self, alarm, new_state, rule_target_alarm):
         transition = alarm.state != new_state
diff --git a/aodh/evaluator/threshold.py b/aodh/evaluator/threshold.py
index 884714b1c..cb9f8071e 100644
--- a/aodh/evaluator/threshold.py
+++ b/aodh/evaluator/threshold.py
@@ -13,21 +13,15 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import copy
 import datetime
 import operator
 import six
 
-from ceilometerclient import client as ceiloclient
-from ceilometerclient import exc as ceiloexc
 from oslo_config import cfg
 from oslo_log import log
 from oslo_utils import timeutils
 
 from aodh import evaluator
-from aodh.evaluator import utils
-from aodh.i18n import _
-from aodh import keystone_client
 
 LOG = log.getLogger(__name__)
 
@@ -63,86 +57,20 @@ class ThresholdEvaluator(evaluator.Evaluator):
     # with 'additional_ingestion_lag' seconds if needed.
     look_back = 1
 
-    def __init__(self, conf):
-        super(ThresholdEvaluator, self).__init__(conf)
-        self._cm_client = None
-
-    @property
-    def cm_client(self):
-        if self._cm_client is None:
-            auth_config = self.conf.service_credentials
-            self._cm_client = ceiloclient.get_client(
-                version=2,
-                session=keystone_client.get_session(self.conf),
-                # ceiloclient adapter options
-                region_name=auth_config.region_name,
-                interface=auth_config.interface,
-            )
-
-        return self._cm_client
-
     def _bound_duration(self, rule):
         """Bound the duration of the statistics query."""
         now = timeutils.utcnow()
         # when exclusion of weak datapoints is enabled, we extend
         # the look-back period so as to allow a clearer sample count
         # trend to be established
-        look_back = (self.look_back if not rule.get('exclude_outliers')
-                     else rule['evaluation_periods'])
         window = ((rule.get('period', None) or rule['granularity'])
-                  * (rule['evaluation_periods'] + look_back) +
+                  * (rule['evaluation_periods'] + self.look_back) +
                   self.conf.additional_ingestion_lag)
         start = now - datetime.timedelta(seconds=window)
         LOG.debug('query stats from %(start)s to '
                   '%(now)s', {'start': start, 'now': now})
         return start.isoformat(), now.isoformat()
 
-    @staticmethod
-    def _sanitize(rule, statistics):
-        """Sanitize statistics."""
-        LOG.debug('sanitize stats %s', statistics)
-        if rule.get('exclude_outliers'):
-            key = operator.attrgetter('count')
-            mean = utils.mean(statistics, key)
-            stddev = utils.stddev(statistics, key, mean)
-            lower = mean - 2 * stddev
-            upper = mean + 2 * stddev
-            inliers, outliers = utils.anomalies(statistics, key, lower, upper)
-            if outliers:
-                LOG.debug('excluded weak datapoints with sample counts %s',
-                          [s.count for s in outliers])
-                statistics = inliers
-            else:
-                LOG.debug('no excluded weak datapoints')
-
-        # in practice statistics are always sorted by period start, not
-        # strictly required by the API though
-        statistics = statistics[-rule['evaluation_periods']:]
-        result_statistics = [getattr(stat, rule['statistic'])
-                             for stat in statistics]
-        LOG.debug('pruned statistics to %d', len(statistics))
-        return result_statistics
-
-    def _statistics(self, rule, start, end):
-        """Retrieve statistics over the current window."""
-        after = dict(field='timestamp', op='ge', value=start)
-        before = dict(field='timestamp', op='le', value=end)
-        query = copy.copy(rule['query'])
-        query.extend([before, after])
-        LOG.debug('stats query %s', query)
-        try:
-            return self.cm_client.statistics.list(
-                meter_name=rule['meter_name'], q=query,
-                period=rule['period'])
-        except Exception as e:
-            if isinstance(e, ceiloexc.HTTPException) and e.code == 410:
-                LOG.warning("This telemetry installation is not configured to "
-                            "support alarm of type 'threshold', they should "
-                            "be disabled or removed.")
-            else:
-                LOG.exception(_('alarm stats retrieval failed'))
-            return []
-
     @staticmethod
     def _reason_data(disposition, count, most_recent):
         """Create a reason data dictionary for this evaluator type."""
diff --git a/aodh/tests/unit/evaluator/base.py b/aodh/tests/unit/evaluator/base.py
index 047be2b81..1363819b4 100644
--- a/aodh/tests/unit/evaluator/base.py
+++ b/aodh/tests/unit/evaluator/base.py
@@ -13,7 +13,6 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations
 # under the License.
-import fixtures
 import mock
 from oslo_config import fixture
 from oslotest import base
@@ -26,10 +25,6 @@ class TestEvaluatorBase(base.BaseTestCase):
         super(TestEvaluatorBase, self).setUp()
         conf = service.prepare_service(argv=[], config_files=[])
         self.conf = self.useFixture(fixture.Config(conf)).conf
-        self.api_client = mock.Mock()
-        self.useFixture(
-            fixtures.MockPatch('ceilometerclient.client.get_client',
-                               return_value=self.api_client))
         self.evaluator = self.EVALUATOR(self.conf)
         self.notifier = mock.MagicMock()
         self.evaluator.notifier = self.notifier
diff --git a/aodh/tests/unit/evaluator/test_composite.py b/aodh/tests/unit/evaluator/test_composite.py
index 1d37ba93d..84ae34fb6 100644
--- a/aodh/tests/unit/evaluator/test_composite.py
+++ b/aodh/tests/unit/evaluator/test_composite.py
@@ -13,7 +13,6 @@
 """Tests for aodh/evaluator/composite.py
 """
 
-from ceilometerclient.v2 import statistics
 import fixtures
 import mock
 from oslo_utils import timeutils
@@ -37,10 +36,6 @@ class BaseCompositeEvaluate(base.TestEvaluatorBase):
         )).mock.Client.return_value
         super(BaseCompositeEvaluate, self).setUp()
 
-    @staticmethod
-    def _get_stats(attr, value, count=1):
-        return statistics.Statistics(None, {attr: value, 'count': count})
-
     @staticmethod
     def _get_gnocchi_stats(granularity, values):
         now = timeutils.utcnow_ts()
diff --git a/doc/source/admin/telemetry-alarms.rst b/doc/source/admin/telemetry-alarms.rst
index 87f55f4dd..4c16be77e 100644
--- a/doc/source/admin/telemetry-alarms.rst
+++ b/doc/source/admin/telemetry-alarms.rst
@@ -47,11 +47,6 @@ Valid threshold alarms are: ``gnocchi_resources_threshold_rule``,
 ``gnocchi_aggregation_by_metrics_threshold_rule``, or
 ``gnocchi_aggregation_by_resources_threshold_rule``.
 
-.. note::
-
-  As of Ocata, the ``threshold`` alarm is deprecated since Ceilometer's
-  native storage API is deprecated.
-
 Composite rule alarms
 ---------------------
 
diff --git a/doc/source/contributor/webapi/v2.rst b/doc/source/contributor/webapi/v2.rst
index 6fffc6ee3..601b217b6 100644
--- a/doc/source/contributor/webapi/v2.rst
+++ b/doc/source/contributor/webapi/v2.rst
@@ -32,9 +32,6 @@ Alarms
 .. autotype:: aodh.api.controllers.v2.alarms.Alarm
    :members:
 
-.. autotype:: aodh.api.controllers.v2.alarm_rules.threshold.AlarmThresholdRule
-   :members:
-
 .. autotype:: aodh.api.controllers.v2.alarm_rules.gnocchi.MetricOfResourceRule
    :members:
 
@@ -100,6 +97,9 @@ field to specify the rule type, like in the following sample::
 
    {
        "threshold": 0.8,
-       "meter_name": "cpu_util",
-       "type": "threshold"
+       "meters": [
+           "f6857d3f-bde6-441a-aa1d-e98fa4ea543f",
+           "ea1491ca-5309-4b5a-9f05-34409c6e8b6c"
+       ],
+       "type": "gnocchi_resources_threshold"
    }
diff --git a/releasenotes/notes/remove-threshold-alarm-a7901991d2da09f2.yaml b/releasenotes/notes/remove-threshold-alarm-a7901991d2da09f2.yaml
new file mode 100644
index 000000000..4c793c5b1
--- /dev/null
+++ b/releasenotes/notes/remove-threshold-alarm-a7901991d2da09f2.yaml
@@ -0,0 +1,4 @@
+---
+deprecations:
+  - |
+    The deprecated 'threshold' alarm type has been removed.
diff --git a/requirements.txt b/requirements.txt
index 362c6b6f4..d5eb10fa8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,7 +21,6 @@ pecan>=0.8.0
 oslo.messaging>=5.2.0 # Apache-2.0
 oslo.middleware>=3.22.0 # Apache-2.0
 oslo.utils>=3.5.0 # Apache-2.0
-python-ceilometerclient>=1.5.0
 python-keystoneclient>=1.6.0
 pytz>=2013.6
 requests>=2.5.2
diff --git a/setup.cfg b/setup.cfg
index 250d52992..61dde4d62 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -68,7 +68,6 @@ aodh.storage =
     sqlite = aodh.storage.impl_sqlalchemy:Connection
 
 aodh.alarm.rule =
-    threshold = aodh.api.controllers.v2.alarm_rules.threshold:AlarmThresholdRule
     gnocchi_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:MetricOfResourceRule
     gnocchi_aggregation_by_metrics_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricsByIdLookupRule
     gnocchi_aggregation_by_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricByResourcesLookupRule
@@ -76,7 +75,6 @@ aodh.alarm.rule =
     composite = aodh.api.controllers.v2.alarm_rules.composite:composite_rule
 
 aodh.evaluator =
-    threshold = aodh.evaluator.threshold:ThresholdEvaluator
     gnocchi_resources_threshold = aodh.evaluator.gnocchi:GnocchiResourceThresholdEvaluator
     gnocchi_aggregation_by_metrics_threshold = aodh.evaluator.gnocchi:GnocchiAggregationMetricsThresholdEvaluator
     gnocchi_aggregation_by_resources_threshold = aodh.evaluator.gnocchi:GnocchiAggregationResourcesThresholdEvaluator