diff --git a/openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.js b/openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.js
new file mode 100644
index 0000000000..ca2f4cb3a4
--- /dev/null
+++ b/openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.js
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+(function () {
+ 'use strict';
+
+ angular
+ .module('horizon.app.core.cloud-services')
+ .directive('hzIfCinderExtensions', hzCinderExtensions);
+
+ hzCinderExtensions.$inject = [
+ 'hzPromiseToggleTemplateDirective',
+ 'horizon.app.core.openstack-service-api.cinderExtensions'
+ ];
+
+ /**
+ * @ngdoc directive
+ * @name hz.api:directive:hzIfCinderExtensions
+ * @module hz.api
+ * @description
+ *
+ * Add this directive to any element containing content which should
+ * only be evaluated when the specified cinder extensions are enabled by
+ * the cinder servers in the currently selected region. If the cinder extensions
+ * are enabled, the content will be evaluated. Otherwise, the content will
+ * not be compiled. In addition, the element and everything contained by
+ * it will be removed completely, leaving a simple HTML comment.
+ *
+ * This is evaluated once per page load. In current horizon, this means
+ * it will get re-evaluated with events like the user opening another panel,
+ * changing logins, or changing their region.
+ *
+ * The hz-if-cinder-extensions attribute may be set to a single extension (String)
+ * or an array of extensions (each one being a String).
+ * All of the following are examples:
+ *
+ * hz-if-cinder-extensions='"SchedulerHints"'
+ * hz-if-cinder-extensions='["SchedulerHints"]'
+ * hz-if-cinder-extensions='["SchedulerHints", "DiskConfig"]'
+ *
+ * @example
+ *
+ * In the below, if the SchedulerHints cinder extension is not enabled, then
+ * the div element with hz-if-cinder-extensions and all of the elements inside
+ * of it will be removed and never evaluated by the angular compiler.
+ *
+ ```html
+
+
+
+ ```
+ */
+ function hzCinderExtensions(hzPromiseToggleTemplateDirective, cinderExtensions) {
+ return angular.extend(
+ hzPromiseToggleTemplateDirective[0],
+ {
+ singlePromiseResolver: cinderExtensions.ifNameEnabled,
+ name: 'hzIfCinderExtensions'
+ }
+ );
+ }
+
+})();
diff --git a/openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.spec.js b/openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.spec.js
new file mode 100644
index 0000000000..4ee7a37fb9
--- /dev/null
+++ b/openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.spec.js
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+(function () {
+ 'use strict';
+
+ describe('horizon.app.core.cloud-services.hzCinderExtension', function () {
+ var $compile, $scope, deferred, cinderExtensionsAPI;
+
+ var template = [
+ ''
+ ].join('');
+
+ beforeEach(function() {
+ cinderExtensionsAPI = {
+ ifNameEnabled: function() {
+ return deferred.promise;
+ }
+ };
+
+ spyOn(cinderExtensionsAPI, 'ifNameEnabled').and.callThrough();
+
+ module('horizon.app.core.cloud-services');
+ module('horizon.framework.util.promise-toggle');
+
+ module('horizon.app.core.openstack-service-api', function($provide) {
+ $provide.value(
+ 'horizon.app.core.openstack-service-api.cinderExtensions', cinderExtensionsAPI
+ );
+ });
+
+ inject(function (_$compile_, _$q_, _$rootScope_) {
+ $compile = _$compile_;
+ deferred = _$q_.defer();
+ $scope = _$rootScope_.$new();
+ });
+
+ });
+
+ // Please note, this code is primarily intended to verify that the
+ // directive specifies the correct name and that it uses the settings
+ // service API. Testing of the variations on inputs being resolved
+ // are tested in the hz-promise-toggle spec.
+ it('should evaluate child elements when extension is enabled', function () {
+ var element = $compile(template)($scope);
+
+ deferred.resolve();
+
+ expect(element.children().length).toBe(0);
+ expect(cinderExtensionsAPI.ifNameEnabled).toHaveBeenCalledWith('ext_name');
+
+ $scope.$apply();
+ expect(element.children().length).toBe(1);
+ });
+
+ it('should not evaluate child elements when extension is NOT enabled', function () {
+ var element = $compile(template)($scope);
+
+ deferred.reject();
+
+ expect(element.children().length).toBe(0);
+ expect(cinderExtensionsAPI.ifNameEnabled).toHaveBeenCalledWith('ext_name');
+
+ $scope.$apply();
+ expect(element.children().length).toBe(0);
+ });
+
+ });
+
+})();