diff --git a/cinder/tests/unit/volume/drivers/test_hgst.py b/cinder/tests/unit/volume/drivers/test_hgst.py
new file mode 100644
index 00000000000..51a8345c278
--- /dev/null
+++ b/cinder/tests/unit/volume/drivers/test_hgst.py
@@ -0,0 +1,939 @@
+# Copyright (c) 2015 HGST Inc
+# All Rights Reserved.
+#
+#    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.
+
+
+import mock
+
+from oslo_concurrency import processutils
+from oslo_log import log as logging
+
+from cinder import context
+from cinder import exception
+from cinder import test
+from cinder.volume import configuration as conf
+from cinder.volume.drivers.hgst import HGSTDriver
+from cinder.volume import volume_types
+
+
+LOG = logging.getLogger(__name__)
+
+
+class HGSTTestCase(test.TestCase):
+
+    # Need to mock these since we use them on driver creation
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def setUp(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Set up UUT and all the flags required for later fake_executes."""
+        super(HGSTTestCase, self).setUp()
+        self.stubs.Set(processutils, 'execute', self._fake_execute)
+        self._fail_vgc_cluster = False
+        self._fail_ip = False
+        self._fail_network_list = False
+        self._fail_domain_list = False
+        self._empty_domain_list = False
+        self._fail_host_storage = False
+        self._fail_space_list = False
+        self._fail_space_delete = False
+        self._fail_set_apphosts = False
+        self._fail_extend = False
+        self._request_cancel = False
+        self._return_blocked = 0
+        self.configuration = mock.Mock(spec=conf.Configuration)
+        self.configuration.safe_get = self._fake_safe_get
+        self._reset_configuration()
+        self.driver = HGSTDriver(configuration=self.configuration,
+                                 execute=self._fake_execute)
+
+    def _fake_safe_get(self, value):
+        """Don't throw exception on missing parameters, return None."""
+        try:
+            val = getattr(self.configuration, value)
+        except AttributeError:
+            val = None
+        return val
+
+    def _reset_configuration(self):
+        """Set safe and sane values for config params."""
+        self.configuration.num_volume_device_scan_tries = 1
+        self.configuration.volume_dd_blocksize = '1M'
+        self.configuration.volume_backend_name = 'hgst-1'
+        self.configuration.hgst_storage_servers = 'stor1:gbd0,stor2:gbd0'
+        self.configuration.hgst_net = 'net1'
+        self.configuration.hgst_redundancy = '0'
+        self.configuration.hgst_space_user = 'kane'
+        self.configuration.hgst_space_group = 'xanadu'
+        self.configuration.hgst_space_mode = '0777'
+
+    def _parse_space_create(self, *cmd):
+        """Eats a vgc-cluster space-create command line to a dict."""
+        self.created = {'storageserver': ''}
+        cmd = list(*cmd)
+        while cmd:
+            param = cmd.pop(0)
+            if param == "-n":
+                self.created['name'] = cmd.pop(0)
+            elif param == "-N":
+                self.created['net'] = cmd.pop(0)
+            elif param == "-s":
+                self.created['size'] = cmd.pop(0)
+            elif param == "--redundancy":
+                self.created['redundancy'] = cmd.pop(0)
+            elif param == "--user":
+                self.created['user'] = cmd.pop(0)
+            elif param == "--user":
+                self.created['user'] = cmd.pop(0)
+            elif param == "--group":
+                self.created['group'] = cmd.pop(0)
+            elif param == "--mode":
+                self.created['mode'] = cmd.pop(0)
+            elif param == "-S":
+                self.created['storageserver'] += cmd.pop(0) + ","
+            else:
+                pass
+
+    def _parse_space_extend(self, *cmd):
+        """Eats a vgc-cluster space-extend commandline to a dict."""
+        self.extended = {'storageserver': ''}
+        cmd = list(*cmd)
+        while cmd:
+            param = cmd.pop(0)
+            if param == "-n":
+                self.extended['name'] = cmd.pop(0)
+            elif param == "-s":
+                self.extended['size'] = cmd.pop(0)
+            elif param == "-S":
+                self.extended['storageserver'] += cmd.pop(0) + ","
+            else:
+                pass
+        if self._fail_extend:
+            raise processutils.ProcessExecutionError(exit_code=1)
+        else:
+            return '', ''
+
+    def _parse_space_delete(self, *cmd):
+        """Eats a vgc-cluster space-delete commandline to a dict."""
+        self.deleted = {}
+        cmd = list(*cmd)
+        while cmd:
+            param = cmd.pop(0)
+            if param == "-n":
+                self.deleted['name'] = cmd.pop(0)
+            else:
+                pass
+        if self._fail_space_delete:
+            raise processutils.ProcessExecutionError(exit_code=1)
+        else:
+            return '', ''
+
+    def _parse_space_list(self, *cmd):
+        """Eats a vgc-cluster space-list commandline to a dict."""
+        json = False
+        nameOnly = False
+        cmd = list(*cmd)
+        while cmd:
+            param = cmd.pop(0)
+            if param == "--json":
+                json = True
+            elif param == "--name-only":
+                nameOnly = True
+            elif param == "-n":
+                pass  # Don't use the name here...
+            else:
+                pass
+        if self._fail_space_list:
+            raise processutils.ProcessExecutionError(exit_code=1)
+        elif nameOnly:
+            return "space1\nspace2\nvolume1\n", ''
+        elif json:
+            return HGST_SPACE_JSON, ''
+        else:
+            return '', ''
+
+    def _parse_network_list(self, *cmd):
+        """Eat a network-list command and return error or results."""
+        if self._fail_network_list:
+            raise processutils.ProcessExecutionError(exit_code=1)
+        else:
+            return NETWORK_LIST, ''
+
+    def _parse_domain_list(self, *cmd):
+        """Eat a domain-list command and return error, empty, or results."""
+        if self._fail_domain_list:
+            raise processutils.ProcessExecutionError(exit_code=1)
+        elif self._empty_domain_list:
+            return '', ''
+        else:
+            return "thisserver\nthatserver\nanotherserver\n", ''
+
+    def _fake_execute(self, *cmd, **kwargs):
+        """Sudo hook to catch commands to allow running on all hosts."""
+        cmdlist = list(cmd)
+        exe = cmdlist.pop(0)
+        if exe == 'vgc-cluster':
+            exe = cmdlist.pop(0)
+            if exe == "request-cancel":
+                self._request_cancel = True
+                if self._return_blocked > 0:
+                    return 'Request cancelled', ''
+                else:
+                    raise processutils.ProcessExecutionError(exit_code=1)
+            elif self._fail_vgc_cluster:
+                raise processutils.ProcessExecutionError(exit_code=1)
+            elif exe == "--version":
+                return "HGST Solutions V2.5.0.0.x.x.x.x.x", ''
+            elif exe == "space-list":
+                return self._parse_space_list(cmdlist)
+            elif exe == "space-create":
+                self._parse_space_create(cmdlist)
+                if self._return_blocked > 0:
+                    self._return_blocked = self._return_blocked - 1
+                    out = "VGC_CREATE_000002\nBLOCKED\n"
+                    raise processutils.ProcessExecutionError(stdout=out,
+                                                             exit_code=1)
+                return '', ''
+            elif exe == "space-delete":
+                return self._parse_space_delete(cmdlist)
+            elif exe == "space-extend":
+                return self._parse_space_extend(cmdlist)
+            elif exe == "host-storage":
+                if self._fail_host_storage:
+                    raise processutils.ProcessExecutionError(exit_code=1)
+                return HGST_HOST_STORAGE, ''
+            elif exe == "domain-list":
+                return self._parse_domain_list()
+            elif exe == "network-list":
+                return self._parse_network_list()
+            elif exe == "space-set-apphosts":
+                if self._fail_set_apphosts:
+                    raise processutils.ProcessExecutionError(exit_code=1)
+                return '', ''
+            else:
+                raise NotImplementedError
+        elif exe == 'ip':
+            if self._fail_ip:
+                raise processutils.ProcessExecutionError(exit_code=1)
+            else:
+                return IP_OUTPUT, ''
+        elif exe == 'dd':
+            self.dd_count = -1
+            for p in cmdlist:
+                if 'count=' in p:
+                    self.dd_count = int(p[6:])
+            return DD_OUTPUT, ''
+        else:
+            return '', ''
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_vgc_cluster_not_present(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when vgc-cluster returns an error."""
+        # Should pass
+        self._fail_vgc_cluster = False
+        self.driver.check_for_setup_error()
+        # Should throw exception
+        self._fail_vgc_cluster = True
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_parameter_redundancy_invalid(self, mock_ghn, mock_grnam,
+                                          mock_pwnam):
+        """Test when hgst_redundancy config parameter not 0 or 1."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Should throw exceptions
+        self.configuration.hgst_redundancy = ''
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        self.configuration.hgst_redundancy = 'Fred'
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_parameter_user_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when hgst_space_user doesn't map to UNIX user."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Should throw exceptions
+        mock_pwnam.side_effect = KeyError()
+        self.configuration.hgst_space_user = ''
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        self.configuration.hgst_space_user = 'Fred!`'
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_parameter_group_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when hgst_space_group doesn't map to UNIX group."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Should throw exceptions
+        mock_grnam.side_effect = KeyError()
+        self.configuration.hgst_space_group = ''
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        self.configuration.hgst_space_group = 'Fred!`'
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_parameter_mode_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when mode for created spaces isn't proper format."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Should throw exceptions
+        self.configuration.hgst_space_mode = ''
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        self.configuration.hgst_space_mode = 'Fred'
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_parameter_net_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when hgst_net not in the domain."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Should throw exceptions
+        self._fail_network_list = True
+        self.configuration.hgst_net = 'Fred'
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        self._fail_network_list = False
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_ip_addr_fails(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when IP ADDR command fails."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Throw exception, need to clear internal cached host in driver
+        self._fail_ip = True
+        self.driver._vgc_host = None
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_domain_list_fails(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when domain-list fails for the domain."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Throw exception, need to clear internal cached host in driver
+        self._fail_domain_list = True
+        self.driver._vgc_host = None
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_not_in_domain(self, mock_ghn, mock_grnam, mock_pwnam):
+        """Test exception when Cinder host not domain member."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Throw exception, need to clear internal cached host in driver
+        self._empty_domain_list = True
+        self.driver._vgc_host = None
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    @mock.patch('pwd.getpwnam', return_value=1)
+    @mock.patch('grp.getgrnam', return_value=1)
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_parameter_storageservers_invalid(self, mock_ghn, mock_grnam,
+                                              mock_pwnam):
+        """Test exception when the storage servers are invalid/missing."""
+        # Should pass
+        self.driver.check_for_setup_error()
+        # Storage_hosts missing
+        self.configuration.hgst_storage_servers = ''
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        # missing a : between host and devnode
+        self.configuration.hgst_storage_servers = 'stor1,stor2'
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        # missing a : between host and devnode
+        self.configuration.hgst_storage_servers = 'stor1:gbd0,stor2'
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+        # Host not in cluster
+        self.configuration.hgst_storage_servers = 'stor1:gbd0'
+        self._fail_host_storage = True
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.check_for_setup_error)
+
+    def test_update_volume_stats(self):
+        """Get cluster space available, should pass."""
+        actual = self.driver.get_volume_stats(True)
+        self.assertEqual('HGST', actual['vendor_name'])
+        self.assertEqual('hgst', actual['storage_protocol'])
+        self.assertEqual(90, actual['total_capacity_gb'])
+        self.assertEqual(87, actual['free_capacity_gb'])
+        self.assertEqual(0, actual['reserved_percentage'])
+
+    def test_update_volume_stats_redundancy(self):
+        """Get cluster space available, half-sized - 1 for mirrors."""
+        self.configuration.hgst_redundancy = '1'
+        actual = self.driver.get_volume_stats(True)
+        self.assertEqual('HGST', actual['vendor_name'])
+        self.assertEqual('hgst', actual['storage_protocol'])
+        self.assertEqual(44, actual['total_capacity_gb'])
+        self.assertEqual(43, actual['free_capacity_gb'])
+        self.assertEqual(0, actual['reserved_percentage'])
+
+    def test_update_volume_stats_cached(self):
+        """Get cached cluster space, should not call executable."""
+        self._fail_host_storage = True
+        actual = self.driver.get_volume_stats(False)
+        self.assertEqual('HGST', actual['vendor_name'])
+        self.assertEqual('hgst', actual['storage_protocol'])
+        self.assertEqual(90, actual['total_capacity_gb'])
+        self.assertEqual(87, actual['free_capacity_gb'])
+        self.assertEqual(0, actual['reserved_percentage'])
+
+    def test_update_volume_stats_error(self):
+        """Test that when host-storage gives an error, return unknown."""
+        self._fail_host_storage = True
+        actual = self.driver.get_volume_stats(True)
+        self.assertEqual('HGST', actual['vendor_name'])
+        self.assertEqual('hgst', actual['storage_protocol'])
+        self.assertEqual('unknown', actual['total_capacity_gb'])
+        self.assertEqual('unknown', actual['free_capacity_gb'])
+        self.assertEqual(0, actual['reserved_percentage'])
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_create_volume(self, mock_ghn):
+        """Test volume creation, ensure appropriate size expansion/name."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10}
+        ret = self.driver.create_volume(volume)
+        expected = {'redundancy': '0', 'group': 'xanadu',
+                    'name': 'volume10', 'mode': '0777',
+                    'user': 'kane', 'net': 'net1',
+                    'storageserver': 'stor1:gbd0,stor2:gbd0,',
+                    'size': '12'}
+        self.assertDictMatch(expected, self.created)
+        # Check the returned provider, note the the provider_id is hashed
+        expected_pid = {'provider_id': 'volume10'}
+        self.assertDictMatch(expected_pid, ret)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_create_volume_name_creation_fail(self, mock_ghn):
+        """Test volume creation exception when can't make a hashed name."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10}
+        self._fail_space_list = True
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.create_volume, volume)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_create_snapshot(self, mock_ghn):
+        """Test creating a snapshot, ensure full data of original copied."""
+        # Now snapshot the volume and check commands
+        snapshot = {'volume_name': 'volume10', 'volume_size': 10,
+                    'volume_id': 'xxx', 'display_name': 'snap10',
+                    'name': '123abc', 'volume_size': 10, 'id': '123abc',
+                    'volume': {'provider_id': 'space10'}}
+        ret = self.driver.create_snapshot(snapshot)
+        # We must copy entier underlying storage, ~12GB, not just 10GB
+        self.assertEqual(11444, self.dd_count)
+        # Check space-create command
+        expected = {'redundancy': '0', 'group': 'xanadu',
+                    'name': snapshot['display_name'], 'mode': '0777',
+                    'user': 'kane', 'net': 'net1',
+                    'storageserver': 'stor1:gbd0,stor2:gbd0,',
+                    'size': '12'}
+        self.assertDictMatch(expected, self.created)
+        # Check the returned provider
+        expected_pid = {'provider_id': 'snap10'}
+        self.assertDictMatch(expected_pid, ret)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_create_cloned_volume(self, mock_ghn):
+        """Test creating a clone, ensure full size is copied from original."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        orig = {'id': '1', 'name': 'volume1', 'display_name': '',
+                'volume_type_id': type_ref['id'], 'size': 10,
+                'provider_id': 'space_orig'}
+        clone = {'id': '2', 'name': 'clone1', 'display_name': '',
+                 'volume_type_id': type_ref['id'], 'size': 10}
+        pid = self.driver.create_cloned_volume(clone, orig)
+        # We must copy entier underlying storage, ~12GB, not just 10GB
+        self.assertEqual(11444, self.dd_count)
+        # Check space-create command
+        expected = {'redundancy': '0', 'group': 'xanadu',
+                    'name': 'clone1', 'mode': '0777',
+                    'user': 'kane', 'net': 'net1',
+                    'storageserver': 'stor1:gbd0,stor2:gbd0,',
+                    'size': '12'}
+        self.assertDictMatch(expected, self.created)
+        # Check the returned provider
+        expected_pid = {'provider_id': 'clone1'}
+        self.assertDictMatch(expected_pid, pid)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_add_cinder_apphosts_fails(self, mock_ghn):
+        """Test exception when set-apphost can't connect volume to host."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        orig = {'id': '1', 'name': 'volume1', 'display_name': '',
+                'volume_type_id': type_ref['id'], 'size': 10,
+                'provider_id': 'space_orig'}
+        clone = {'id': '2', 'name': 'clone1', 'display_name': '',
+                 'volume_type_id': type_ref['id'], 'size': 10}
+        self._fail_set_apphosts = True
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.create_cloned_volume, clone, orig)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_create_volume_from_snapshot(self, mock_ghn):
+        """Test creating volume from snapshot, ensure full space copy."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        snap = {'id': '1', 'name': 'volume1', 'display_name': '',
+                'volume_type_id': type_ref['id'], 'size': 10,
+                'provider_id': 'space_orig'}
+        volume = {'id': '2', 'name': 'volume2', 'display_name': '',
+                  'volume_type_id': type_ref['id'], 'size': 10}
+        pid = self.driver.create_volume_from_snapshot(volume, snap)
+        # We must copy entier underlying storage, ~12GB, not just 10GB
+        self.assertEqual(11444, self.dd_count)
+        # Check space-create command
+        expected = {'redundancy': '0', 'group': 'xanadu',
+                    'name': 'volume2', 'mode': '0777',
+                    'user': 'kane', 'net': 'net1',
+                    'storageserver': 'stor1:gbd0,stor2:gbd0,',
+                    'size': '12'}
+        self.assertDictMatch(expected, self.created)
+        # Check the returned provider
+        expected_pid = {'provider_id': 'volume2'}
+        self.assertDictMatch(expected_pid, pid)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_create_volume_blocked(self, mock_ghn):
+        """Test volume creation where only initial space-create is blocked.
+
+        This should actually pass because we are blocked byt return an error
+        in request-cancel, meaning that it got unblocked before we could kill
+        the space request.
+        """
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10}
+        self._return_blocked = 1  # Block & fail cancel => create succeeded
+        ret = self.driver.create_volume(volume)
+        expected = {'redundancy': '0', 'group': 'xanadu',
+                    'name': 'volume10', 'mode': '0777',
+                    'user': 'kane', 'net': 'net1',
+                    'storageserver': 'stor1:gbd0,stor2:gbd0,',
+                    'size': '12'}
+        self.assertDictMatch(expected, self.created)
+        # Check the returned provider
+        expected_pid = {'provider_id': 'volume10'}
+        self.assertDictMatch(expected_pid, ret)
+        self.assertEqual(True, self._request_cancel)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_create_volume_blocked_and_fail(self, mock_ghn):
+        """Test volume creation where space-create blocked permanently.
+
+        This should fail because the initial create was blocked and the
+        request-cancel succeeded, meaning the create operation never
+        completed.
+        """
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10}
+        self._return_blocked = 2  # Block & pass cancel => create failed. :(
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.create_volume, volume)
+        self.assertEqual(True, self._request_cancel)
+
+    def test_delete_volume(self):
+        """Test deleting existing volume, ensure proper name used."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10,
+                  'provider_id': 'volume10'}
+        self.driver.delete_volume(volume)
+        expected = {'name': 'volume10'}
+        self.assertDictMatch(expected, self.deleted)
+
+    def test_delete_volume_failure_modes(self):
+        """Test cases where space-delete fails, but OS delete is still OK."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10,
+                  'provider_id': 'volume10'}
+        self._fail_space_delete = True
+        # This should not throw an exception, space-delete failure not problem
+        self.driver.delete_volume(volume)
+        self._fail_space_delete = False
+        volume['provider_id'] = None
+        # This should also not throw an exception
+        self.driver.delete_volume(volume)
+
+    def test_delete_snapshot(self):
+        """Test deleting a snapshot, ensure proper name is removed."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        snapshot = {'id': '1', 'name': 'volume1',
+                    'display_name': '',
+                    'volume_type_id': type_ref['id'],
+                    'size': 10,
+                    'provider_id': 'snap10'}
+        self.driver.delete_snapshot(snapshot)
+        expected = {'name': 'snap10'}
+        self.assertDictMatch(expected, self.deleted)
+
+    def test_extend_volume(self):
+        """Test extending a volume, check the size in GB vs. GiB."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10,
+                  'provider_id': 'volume10'}
+        self.extended = {'name': '', 'size': '0',
+                         'storageserver': ''}
+        self.driver.extend_volume(volume, 12)
+        expected = {'name': 'volume10', 'size': '2',
+                    'storageserver': 'stor1:gbd0,stor2:gbd0,'}
+        self.assertDictMatch(expected, self.extended)
+
+    def test_extend_volume_noextend(self):
+        """Test extending a volume where Space does not need to be enlarged.
+
+        Because Spaces are generated somewhat larger than the requested size
+        from OpenStack due to the base10(HGST)/base2(OS) mismatch, they can
+        sometimes be larger than requested from OS.  In that case a
+        volume_extend may actually be a noop since the volume is already large
+        enough to satisfy OS's request.
+        """
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10,
+                  'provider_id': 'volume10'}
+        self.extended = {'name': '', 'size': '0',
+                         'storageserver': ''}
+        self.driver.extend_volume(volume, 10)
+        expected = {'name': '', 'size': '0',
+                    'storageserver': ''}
+        self.assertDictMatch(expected, self.extended)
+
+    def test_space_list_fails(self):
+        """Test exception is thrown when we can't call space-list."""
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10,
+                  'provider_id': 'volume10'}
+        self.extended = {'name': '', 'size': '0',
+                         'storageserver': ''}
+        self._fail_space_list = True
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.extend_volume, volume, 12)
+
+    def test_cli_error_not_blocked(self):
+        """Test the _blocked handler's handlinf of a non-blocked error.
+
+        The _handle_blocked handler is called on any process errors in the
+        code.  If the error was not caused by a blocked command condition
+        (syntax error, out of space, etc.) then it should just throw the
+        exception and not try and retry the command.
+        """
+        ctxt = context.get_admin_context()
+        extra_specs = {}
+        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
+        volume = {'id': '1', 'name': 'volume1',
+                  'display_name': '',
+                  'volume_type_id': type_ref['id'],
+                  'size': 10,
+                  'provider_id': 'volume10'}
+        self.extended = {'name': '', 'size': '0',
+                         'storageserver': ''}
+        self._fail_extend = True
+        self.assertRaises(exception.VolumeDriverException,
+                          self.driver.extend_volume, volume, 12)
+        self.assertEqual(False, self._request_cancel)
+
+    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
+    def test_initialize_connection(self, moch_ghn):
+        """Test that the connection_info for Nova makes sense."""
+        volume = {'name': '123', 'provider_id': 'spacey'}
+        conn = self.driver.initialize_connection(volume, None)
+        expected = {'name': 'spacey', 'noremovehost': 'thisserver'}
+        self.assertDictMatch(expected, conn['data'])
+
+# Below are some command outputs we emulate
+IP_OUTPUT = """
+3: em2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state
+    link/ether 00:25:90:d9:18:09 brd ff:ff:ff:ff:ff:ff
+    inet 192.168.0.23/24 brd 192.168.0.255 scope global em2
+       valid_lft forever preferred_lft forever
+    inet6 fe80::225:90ff:fed9:1809/64 scope link
+       valid_lft forever preferred_lft forever
+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
+    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+    inet 123.123.123.123/8 scope host lo
+       valid_lft forever preferred_lft forever
+    inet 169.254.169.254/32 scope link lo
+       valid_lft forever preferred_lft forever
+    inet6 ::1/128 scope host
+       valid_lft forever preferred_lft forever
+2: em1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master
+    link/ether 00:25:90:d9:18:08 brd ff:ff:ff:ff:ff:ff
+    inet6 fe80::225:90ff:fed9:1808/64 scope link
+       valid_lft forever preferred_lft forever
+"""
+
+HGST_HOST_STORAGE = """
+{
+  "hostStatus": [
+    {
+      "node": "tm33.virident.info",
+      "up": true,
+      "isManager": true,
+      "cardStatus": [
+        {
+          "cardName": "/dev/sda3",
+          "cardSerialNumber": "002f09b4037a9d521c007ee4esda3",
+          "cardStatus": "Good",
+          "cardStateDetails": "Normal",
+          "cardActionRequired": "",
+          "cardTemperatureC": 0,
+          "deviceType": "Generic",
+          "cardTemperatureState": "Safe",
+          "partitionStatus": [
+            {
+              "partName": "/dev/gbd0",
+              "partitionState": "READY",
+              "usableCapacityBytes": 98213822464,
+              "totalReadBytes": 0,
+              "totalWriteBytes": 0,
+              "remainingLifePCT": 100,
+              "flashReservesLeftPCT": 100,
+              "fmc": true,
+              "vspaceCapacityAvailable": 94947041280,
+              "vspaceReducedCapacityAvailable": 87194279936,
+              "_partitionID": "002f09b4037a9d521c007ee4esda3:0",
+              "_usedSpaceBytes": 3266781184,
+              "_enabledSpaceBytes": 3266781184,
+              "_disabledSpaceBytes": 0
+            }
+          ]
+        }
+      ],
+      "driverStatus": {
+        "vgcdriveDriverLoaded": true,
+        "vhaDriverLoaded": true,
+        "vcacheDriverLoaded": true,
+        "vlvmDriverLoaded": true,
+        "ipDataProviderLoaded": true,
+        "ibDataProviderLoaded": false,
+        "driverUptimeSecs": 4800,
+        "rVersion": "20368.d55ec22.master"
+      },
+      "totalCapacityBytes": 98213822464,
+      "totalUsedBytes": 3266781184,
+      "totalEnabledBytes": 3266781184,
+      "totalDisabledBytes": 0
+    },
+    {
+      "node": "tm32.virident.info",
+      "up": true,
+      "isManager": false,
+      "cardStatus": [],
+      "driverStatus": {
+        "vgcdriveDriverLoaded": true,
+        "vhaDriverLoaded": true,
+        "vcacheDriverLoaded": true,
+        "vlvmDriverLoaded": true,
+        "ipDataProviderLoaded": true,
+        "ibDataProviderLoaded": false,
+        "driverUptimeSecs": 0,
+        "rVersion": "20368.d55ec22.master"
+      },
+      "totalCapacityBytes": 0,
+      "totalUsedBytes": 0,
+      "totalEnabledBytes": 0,
+      "totalDisabledBytes": 0
+    }
+  ],
+  "totalCapacityBytes": 98213822464,
+  "totalUsedBytes": 3266781184,
+  "totalEnabledBytes": 3266781184,
+  "totalDisabledBytes": 0
+}
+"""
+
+HGST_SPACE_JSON = """
+{
+  "resources": [
+    {
+      "resourceType": "vLVM-L",
+      "resourceID": "vLVM-L:698cdb43-54da-863e-1699-294a080ce4db",
+      "state": "OFFLINE",
+      "instanceStates": {},
+      "redundancy": 0,
+      "sizeBytes": 12000000000,
+      "name": "volume10",
+      "nodes": [],
+      "networks": [
+        "net1"
+      ],
+      "components": [
+        {
+          "resourceType": "vLVM-S",
+          "resourceID": "vLVM-S:698cdb43-54da-863e-eb10-6275f47b8ed2",
+          "redundancy": 0,
+          "order": 0,
+          "sizeBytes": 12000000000,
+          "numStripes": 1,
+          "stripeSizeBytes": null,
+          "name": "volume10s00",
+          "state": "OFFLINE",
+          "instanceStates": {},
+          "components": [
+            {
+              "name": "volume10h00",
+              "resourceType": "vHA",
+              "resourceID": "vHA:3e86da54-40db-8c69-0300-0000ac10476e",
+              "redundancy": 0,
+              "sizeBytes": 12000000000,
+              "state": "GOOD",
+              "components": [
+                {
+                  "name": "volume10h00",
+                  "vspaceType": "vHA",
+                  "vspaceRole": "primary",
+                  "storageObjectID": "vHA:3e86da54-40db-8c69--18130019e486",
+                  "state": "Disconnected (DCS)",
+                  "node": "tm33.virident.info",
+                  "partName": "/dev/gbd0"
+                }
+              ],
+              "crState": "GOOD"
+            },
+            {
+              "name": "volume10v00",
+              "resourceType": "vShare",
+              "resourceID": "vShare:3f86da54-41db-8c69-0300-ecf4bbcc14cc",
+              "redundancy": 0,
+              "order": 0,
+              "sizeBytes": 12000000000,
+              "state": "GOOD",
+              "components": [
+                {
+                  "name": "volume10v00",
+                  "vspaceType": "vShare",
+                  "vspaceRole": "target",
+                  "storageObjectID": "vShare:3f86da54-41db-8c64bbcc14cc:T",
+                  "state": "Started",
+                  "node": "tm33.virident.info",
+                  "partName": "/dev/gbd0_volume10h00"
+                }
+              ]
+            }
+          ]
+        }
+      ],
+      "_size": "12GB",
+      "_state": "OFFLINE",
+      "_ugm": "",
+      "_nets": "net1",
+      "_hosts": "tm33.virident.info(12GB,NC)",
+      "_ahosts": "",
+      "_shosts": "tm33.virident.info(12GB)",
+      "_name": "volume10",
+      "_node": "",
+      "_type": "vLVM-L",
+      "_detail": "vLVM-L:698cdb43-54da-863e-1699-294a080ce4db",
+      "_device": ""
+    }
+  ]
+}
+"""
+
+NETWORK_LIST = """
+Network Name Type    Flags          Description
+------------ ---- ---------- ------------------------
+net1         IPv4 autoConfig 192.168.0.0/24 1Gb/s
+net2         IPv4 autoConfig 192.168.10.0/24 10Gb/s
+"""
+
+DD_OUTPUT = """
+1+0 records in
+1+0 records out
+1024 bytes (1.0 kB) copied, 0.000427529 s, 2.4 MB/s
+"""
diff --git a/cinder/volume/drivers/hgst.py b/cinder/volume/drivers/hgst.py
new file mode 100644
index 00000000000..59b0b29f2e6
--- /dev/null
+++ b/cinder/volume/drivers/hgst.py
@@ -0,0 +1,602 @@
+# Copyright 2015 HGST
+# All Rights Reserved.
+#
+#    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.
+"""
+Desc    : Driver to store Cinder volumes using HGST Flash Storage Suite
+Require : HGST Flash Storage Suite
+Author  : Earle F. Philhower, III <earle.philhower.iii@hgst.com>
+"""
+
+import grp
+import json
+import math
+import os
+import pwd
+import six
+import socket
+import string
+
+from oslo_concurrency import lockutils
+from oslo_concurrency import processutils
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import units
+
+from cinder import exception
+from cinder.i18n import _
+from cinder.i18n import _LE
+from cinder.i18n import _LW
+from cinder.image import image_utils
+from cinder.volume import driver
+from cinder.volume import utils as volutils
+
+LOG = logging.getLogger(__name__)
+
+hgst_opts = [
+    cfg.StrOpt('hgst_net',
+               default='Net 1 (IPv4)',
+               help='Space network name to use for data transfer'),
+    cfg.StrOpt('hgst_storage_servers',
+               default='os:gbd0',
+               help='Comma separated list of Space storage servers:devices. '
+                    'ex: os1_stor:gbd0,os2_stor:gbd0'),
+    cfg.StrOpt('hgst_redundancy',
+               default='0',
+               help='Should spaces be redundantly stored (1/0)'),
+    cfg.StrOpt('hgst_space_user',
+               default='root',
+               help='User to own created spaces'),
+    cfg.StrOpt('hgst_space_group',
+               default='disk',
+               help='Group to own created spaces'),
+    cfg.StrOpt('hgst_space_mode',
+               default='0600',
+               help='UNIX mode for created spaces'),
+]
+
+
+CONF = cfg.CONF
+CONF.register_opts(hgst_opts)
+
+
+class HGSTDriver(driver.VolumeDriver):
+    """This is the Class to set in cinder.conf (volume_driver).
+
+    Implements a Cinder Volume driver which creates a HGST Space for each
+    Cinder Volume or Snapshot requested.  Use the vgc-cluster CLI to do
+    all management operations.
+
+    The Cinder host will nominally have all Spaces made visible to it,
+    while individual compute nodes will only have Spaces connected to KVM
+    instances connected.
+    """
+
+    VERSION = '1.0.0'
+    VGCCLUSTER = 'vgc-cluster'
+    SPACEGB = units.G - 16 * units.M  # Workaround for shrinkage Bug 28320
+    BLOCKED = "BLOCKED"  # Exit code when a command is blocked
+
+    def __init__(self, *args, **kwargs):
+        """Initialize our protocol descriptor/etc."""
+        super(HGSTDriver, self).__init__(*args, **kwargs)
+        self.configuration.append_config_values(hgst_opts)
+        self._vgc_host = None
+        self.check_for_setup_error()
+        self._stats = {'driver_version': self.VERSION,
+                       'reserved_percentage': 0,
+                       'storage_protocol': 'hgst',
+                       'total_capacity_gb': 'unknown',
+                       'free_capacity_gb': 'unknown',
+                       'vendor_name': 'HGST',
+                       }
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        self._stats['volume_backend_name'] = backend_name or 'hgst'
+        self.update_volume_stats()
+
+    def _log_cli_err(self, err):
+        """Dumps the full command output to a logfile in error cases."""
+        LOG.error(_LE("CLI fail: '%(cmd)s' = %(code)s\nout: %(stdout)s\n"
+                      "err: %(stderr)s"),
+                  {'cmd': err.cmd, 'code': err.exit_code,
+                   'stdout': err.stdout, 'stderr': err.stderr})
+
+    def _find_vgc_host(self):
+        """Finds vgc-cluster hostname for this box."""
+        params = [self.VGCCLUSTER, "domain-list", "-1"]
+        try:
+            out, unused = self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            self._log_cli_err(err)
+            msg = _("Unable to get list of domain members, check that "
+                    "the cluster is running.")
+            raise exception.VolumeDriverException(message=msg)
+        domain = out.splitlines()
+        params = ["ip", "addr", "list"]
+        try:
+            out, unused = self._execute(*params, run_as_root=False)
+        except processutils.ProcessExecutionError as err:
+            self._log_cli_err(err)
+            msg = _("Unable to get list of IP addresses on this host, "
+                    "check permissions and networking.")
+            raise exception.VolumeDriverException(message=msg)
+        nets = out.splitlines()
+        for host in domain:
+            try:
+                ip = socket.gethostbyname(host)
+                for l in nets:
+                    x = l.strip()
+                    if x.startswith("inet %s/" % ip):
+                        return host
+            except socket.error:
+                pass
+        msg = _("Current host isn't part of HGST domain.")
+        raise exception.VolumeDriverException(message=msg)
+
+    def _hostname(self):
+        """Returns hostname to use for cluster operations on this box."""
+        if self._vgc_host is None:
+            self._vgc_host = self._find_vgc_host()
+        return self._vgc_host
+
+    def _make_server_list(self):
+        """Converts a comma list into params for use by HGST CLI."""
+        csv = self.configuration.safe_get('hgst_storage_servers')
+        servers = csv.split(",")
+        params = []
+        for server in servers:
+            params.append('-S')
+            params.append(six.text_type(server))
+        return params
+
+    def _make_space_name(self, name):
+        """Generates the hashed name for the space from the name.
+
+        This must be called in a locked context as there are race conditions
+        where 2 contexts could both pick what they think is an unallocated
+        space name, and fail later on due to that conflict.
+        """
+        # Sanitize the name string
+        valid_chars = "-_.%s%s" % (string.ascii_letters, string.digits)
+        name = ''.join(c for c in name if c in valid_chars)
+        name = name.strip(".")  # Remove any leading .s from evil users
+        name = name or "space"  # In case of all illegal chars, safe default
+        # Start out with just the name, truncated to 14 characters
+        outname = name[0:13]
+        # See what names already defined
+        params = [self.VGCCLUSTER, "space-list", "--name-only"]
+        try:
+            out, unused = self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            self._log_cli_err(err)
+            msg = _("Unable to get list of spaces to make new name.  Please "
+                    "verify the cluster is running.")
+            raise exception.VolumeDriverException(message=msg)
+        names = out.splitlines()
+        # And anything in /dev/* is also illegal
+        names += os.listdir("/dev")  # Do it the Python way!
+        names += ['.', '..']  # Not included above
+        # While there's a conflict, add incrementing digits until it passes
+        itr = 0
+        while outname in names:
+            itrstr = six.text_type(itr)
+            outname = outname[0:13 - len(itrstr)] + itrstr
+            itr += 1
+        return outname
+
+    def _get_space_size_redundancy(self, space_name):
+        """Parse space output to get allocated size and redundancy."""
+        params = [self.VGCCLUSTER, "space-list", "-n", space_name, "--json"]
+        try:
+            out, unused = self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            self._log_cli_err(err)
+            msg = _("Unable to get information on space %(space)s, please "
+                    "verify that the cluster is running and "
+                    "connected.") % {'space': space_name}
+            raise exception.VolumeDriverException(message=msg)
+        ret = json.loads(out)
+        retval = {}
+        retval['redundancy'] = int(ret['resources'][0]['redundancy'])
+        retval['sizeBytes'] = int(ret['resources'][0]['sizeBytes'])
+        return retval
+
+    def _adjust_size_g(self, size_g):
+        """Adjust space size to next legal value because of redundancy."""
+        # Extending requires expanding to a multiple of the # of
+        # storage hosts in the cluster
+        count = len(self._make_server_list()) / 2  # Remove -s from count
+        if size_g % count:
+            size_g = int(size_g + count)
+            size_g -= size_g % count
+        return int(math.ceil(size_g))
+
+    def do_setup(self, context):
+        pass
+
+    def _get_space_name(self, volume):
+        """Pull name of /dev/<space> from the provider_id."""
+        try:
+            return volume.get('provider_id')
+        except Exception:
+            return ''  # Some error during create, may be able to continue
+
+    def _handle_blocked(self, err, msg):
+        """Safely handle a return code of BLOCKED from a cluster command.
+
+        Handle the case where a command is in BLOCKED state by trying to
+        cancel it.  If the cancel fails, then the command actually did
+        complete.  If the cancel succeeds, then throw the original error
+        back up the stack.
+        """
+        if (err.stdout is not None) and (self.BLOCKED in err.stdout):
+            # Command is queued but did not complete in X seconds, so
+            # we will cancel it to keep things sane.
+            request = err.stdout.split('\n', 1)[0].strip()
+            params = [self.VGCCLUSTER, 'request-cancel']
+            params += ['-r', six.text_type(request)]
+            throw_err = False
+            try:
+                self._execute(*params, run_as_root=True)
+                # Cancel succeeded, the command was aborted
+                # Send initial exception up the stack
+                LOG.error(_LE("VGC-CLUSTER command blocked and cancelled."))
+                # Can't throw it here, the except below would catch it!
+                throw_err = True
+            except Exception:
+                # The cancel failed because the command was just completed.
+                # That means there was no failure, so continue with Cinder op
+                pass
+            if throw_err:
+                self._log_cli_err(err)
+                msg = _("Command %(cmd)s blocked in the CLI and was "
+                        "cancelled") % {'cmd': six.text_type(err.cmd)}
+                raise exception.VolumeDriverException(message=msg)
+        else:
+            # Some other error, just throw it up the chain
+            self._log_cli_err(err)
+            raise exception.VolumeDriverException(message=msg)
+
+    def _add_cinder_apphost(self, spacename):
+        """Add this host to the apphost list of a space."""
+        # Connect to source volume
+        params = [self.VGCCLUSTER, 'space-set-apphosts']
+        params += ['-n', spacename]
+        params += ['-A', self._hostname()]
+        params += ['--action', 'ADD']  # Non-error to add already existing
+        try:
+            self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            msg = _("Unable to add Cinder host to apphosts for space "
+                    "%(space)s") % {'space': spacename}
+            self._handle_blocked(err, msg)
+
+    @lockutils.synchronized('devices', 'cinder-hgst-')
+    def create_volume(self, volume):
+        """API entry to create a volume on the cluster as a HGST space.
+
+        Creates a volume, adjusting for GiB/GB sizing.  Locked to ensure we
+        don't have race conditions on the name we pick to use for the space.
+        """
+        # For ease of deugging, use friendly name if it exists
+        volname = self._make_space_name(volume['display_name']
+                                        or volume['name'])
+        volnet = self.configuration.safe_get('hgst_net')
+        volbytes = volume['size'] * units.Gi  # OS=Base2, but HGST=Base10
+        volsize_gb_cinder = int(math.ceil(float(volbytes) /
+                                float(self.SPACEGB)))
+        volsize_g = self._adjust_size_g(volsize_gb_cinder)
+        params = [self.VGCCLUSTER, 'space-create']
+        params += ['-n', six.text_type(volname)]
+        params += ['-N', six.text_type(volnet)]
+        params += ['-s', six.text_type(volsize_g)]
+        params += ['--redundancy', six.text_type(
+                   self.configuration.safe_get('hgst_redundancy'))]
+        params += ['--user', six.text_type(
+                   self.configuration.safe_get('hgst_space_user'))]
+        params += ['--group', six.text_type(
+                   self.configuration.safe_get('hgst_space_group'))]
+        params += ['--mode', six.text_type(
+                   self.configuration.safe_get('hgst_space_mode'))]
+        params += self._make_server_list()
+        params += ['-A', self._hostname()]  # Make it visible only here
+        try:
+            self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            msg = _("Error in space-create for %(space)s of size "
+                    "%(size)d GB") % {'space': volname,
+                                      'size': int(volsize_g)}
+            self._handle_blocked(err, msg)
+        # Stash away the hashed name
+        provider = {}
+        provider['provider_id'] = volname
+        return provider
+
+    def update_volume_stats(self):
+        """Parse the JSON output of vgc-cluster to find space available."""
+        params = [self.VGCCLUSTER, "host-storage", "--json"]
+        try:
+            out, unused = self._execute(*params, run_as_root=True)
+            ret = json.loads(out)
+            cap = int(ret["totalCapacityBytes"] / units.Gi)
+            used = int(ret["totalUsedBytes"] / units.Gi)
+            avail = cap - used
+            if int(self.configuration.safe_get('hgst_redundancy')) == 1:
+                cap = int(cap / 2)
+                avail = int(avail / 2)
+            # Reduce both by 1 GB due to BZ 28320
+            if cap > 0:
+                cap = cap - 1
+            if avail > 0:
+                avail = avail - 1
+        except processutils.ProcessExecutionError as err:
+            # Could be cluster still starting up, return unknown for now
+            LOG.warning(_LW("Unable to poll cluster free space."))
+            self._log_cli_err(err)
+            cap = 'unknown'
+            avail = 'unknown'
+        self._stats['free_capacity_gb'] = avail
+        self._stats['total_capacity_gb'] = cap
+        self._stats['reserved_percentage'] = 0
+
+    def get_volume_stats(self, refresh=False):
+        """Return Volume statistics, potentially cached copy."""
+        if refresh:
+            self.update_volume_stats()
+        return self._stats
+
+    def create_cloned_volume(self, volume, src_vref):
+        """Create a cloned volume from an existing one.
+
+        No cloning operation in the current release so simply copy using
+        DD to a new space.  This could be a lengthy operation.
+        """
+        # Connect to source volume
+        volname = self._get_space_name(src_vref)
+        self._add_cinder_apphost(volname)
+
+        # Make new volume
+        provider = self.create_volume(volume)
+        self._add_cinder_apphost(provider['provider_id'])
+
+        # And copy original into it...
+        info = self._get_space_size_redundancy(volname)
+        volutils.copy_volume(
+            self.local_path(src_vref),
+            "/dev/" + provider['provider_id'],
+            info['sizeBytes'] / units.Mi,
+            self.configuration.volume_dd_blocksize,
+            execute=self._execute)
+
+        # That's all, folks!
+        return provider
+
+    def copy_image_to_volume(self, context, volume, image_service, image_id):
+        """Fetch the image from image_service and write it to the volume."""
+        image_utils.fetch_to_raw(context,
+                                 image_service,
+                                 image_id,
+                                 self.local_path(volume),
+                                 self.configuration.volume_dd_blocksize,
+                                 size=volume['size'])
+
+    def copy_volume_to_image(self, context, volume, image_service, image_meta):
+        """Copy the volume to the specified image."""
+        image_utils.upload_volume(context,
+                                  image_service,
+                                  image_meta,
+                                  self.local_path(volume))
+
+    def delete_volume(self, volume):
+        """Delete a Volume's underlying space."""
+        volname = self._get_space_name(volume)
+        if volname:
+            params = [self.VGCCLUSTER, 'space-delete']
+            params += ['-n', six.text_type(volname)]
+            # This can fail benignly when we are deleting a snapshot
+            try:
+                self._execute(*params, run_as_root=True)
+            except processutils.ProcessExecutionError as err:
+                LOG.warning(_LW("Unable to delete space %(space)s"),
+                            {'space': volname})
+                self._log_cli_err(err)
+        else:
+            # This can be benign when we are deleting a snapshot
+            LOG.warning(_LW("Attempted to delete a space that's not there."))
+
+    def _check_host_storage(self, server):
+        if ":" not in server:
+            msg = _("hgst_storage server %(svr)s not of format "
+                    "<host>:<dev>") % {'svr': server}
+            raise exception.VolumeDriverException(message=msg)
+        h, b = server.split(":")
+        try:
+            params = [self.VGCCLUSTER, 'host-storage', '-h', h]
+            self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            self._log_cli_err(err)
+            msg = _("Storage host %(svr)s not detected, verify "
+                    "name") % {'svr': six.text_type(server)}
+            raise exception.VolumeDriverException(message=msg)
+
+    def check_for_setup_error(self):
+        """Throw an exception if configuration values/setup isn't okay."""
+        # Verify vgc-cluster exists and is executable by cinder user
+        try:
+            params = [self.VGCCLUSTER, '--version']
+            self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            self._log_cli_err(err)
+            msg = _("Cannot run vgc-cluster command, please ensure software "
+                    "is installed and permissions are set properly.")
+            raise exception.VolumeDriverException(message=msg)
+
+        # Checks the host is identified with the HGST domain, as well as
+        # that vgcnode and vgcclustermgr services are running.
+        self._vgc_host = None
+        self._hostname()
+
+        # Redundancy better be 0 or 1, otherwise no comprendo
+        r = six.text_type(self.configuration.safe_get('hgst_redundancy'))
+        if r not in ["0", "1"]:
+            msg = _("hgst_redundancy must be set to 0 (non-HA) or 1 (HA) in "
+                    "cinder.conf.")
+            raise exception.VolumeDriverException(message=msg)
+
+        # Verify user and group exist or we can't connect volumes
+        try:
+            pwd.getpwnam(self.configuration.safe_get('hgst_space_user'))
+            grp.getgrnam(self.configuration.safe_get('hgst_space_group'))
+        except KeyError as err:
+            msg = _("hgst_group %(grp)s and hgst_user %(usr)s must map to "
+                    "valid users/groups in cinder.conf") % {
+                'grp': self.configuration.safe_get('hgst_space_group'),
+                'usr': self.configuration.safe_get('hgst_space_user')}
+            raise exception.VolumeDriverException(message=msg)
+
+        # Verify mode is a nicely formed octal or integer
+        try:
+            int(self.configuration.safe_get('hgst_space_mode'))
+        except Exception as err:
+            msg = _("hgst_space_mode must be an octal/int in cinder.conf")
+            raise exception.VolumeDriverException(message=msg)
+
+        # Validate network maps to something we know about
+        try:
+            params = [self.VGCCLUSTER, 'network-list']
+            params += ['-N', self.configuration.safe_get('hgst_net')]
+            self._execute(*params, run_as_root=True)
+        except processutils.ProcessExecutionError as err:
+            self._log_cli_err(err)
+            msg = _("hgst_net %(net)s specified in cinder.conf not found "
+                    "in cluster") % {
+                'net': self.configuration.safe_get('hgst_net')}
+            raise exception.VolumeDriverException(message=msg)
+
+        # Storage servers require us to split them up and check for
+        sl = self.configuration.safe_get('hgst_storage_servers')
+        if (sl is None) or (six.text_type(sl) == ""):
+            msg = _("hgst_storage_servers must be defined in cinder.conf")
+            raise exception.VolumeDriverException(message=msg)
+        servers = sl.split(",")
+        # Each server must be of the format <host>:<storage> w/host in domain
+        for server in servers:
+            self._check_host_storage(server)
+
+        # We made it here, we should be good to go!
+        return True
+
+    def create_snapshot(self, snapshot):
+        """Create a snapshot volume.
+
+        We don't yet support snaps in SW so make a new volume and dd the
+        source one into it.  This could be a lengthy operation.
+        """
+        origvol = {}
+        origvol['name'] = snapshot['volume_name']
+        origvol['size'] = snapshot['volume_size']
+        origvol['id'] = snapshot['volume_id']
+        origvol['provider_id'] = snapshot.get('volume').get('provider_id')
+        # Add me to the apphosts so I can see the volume
+        self._add_cinder_apphost(self._get_space_name(origvol))
+
+        # Make snapshot volume
+        snapvol = {}
+        snapvol['display_name'] = snapshot['display_name']
+        snapvol['name'] = snapshot['name']
+        snapvol['size'] = snapshot['volume_size']
+        snapvol['id'] = snapshot['id']
+        provider = self.create_volume(snapvol)
+        # Create_volume attaches the volume to this host, ready to snapshot.
+        # Copy it using dd for now, we don't have real snapshots
+        # We need to copy the entire allocated volume space, Nova will allow
+        # full access, even beyond requested size (when our volume is larger
+        # due to our ~1B byte alignment or cluster makeup)
+        info = self._get_space_size_redundancy(origvol['provider_id'])
+        volutils.copy_volume(
+            self.local_path(origvol),
+            "/dev/" + provider['provider_id'],
+            info['sizeBytes'] / units.Mi,
+            self.configuration.volume_dd_blocksize,
+            execute=self._execute)
+        return provider
+
+    def delete_snapshot(self, snapshot):
+        """Delete a snapshot.  For now, snapshots are full volumes."""
+        self.delete_volume(snapshot)
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create volume from a snapshot, but snaps still full volumes."""
+        return self.create_cloned_volume(volume, snapshot)
+
+    def extend_volume(self, volume, new_size):
+        """Extend an existing volume.
+
+        We may not actually need to resize the space because it's size is
+        always rounded up to a function of the GiB/GB and number of storage
+        nodes.
+        """
+        volname = self._get_space_name(volume)
+        info = self._get_space_size_redundancy(volname)
+        volnewbytes = new_size * units.Gi
+        new_size_g = math.ceil(float(volnewbytes) / float(self.SPACEGB))
+        wantedsize_g = self._adjust_size_g(new_size_g)
+        havesize_g = (info['sizeBytes'] / self.SPACEGB)
+        if havesize_g >= wantedsize_g:
+            return  # Already big enough, happens with redundancy
+        else:
+            # Have to extend it
+            delta = int(wantedsize_g - havesize_g)
+            params = [self.VGCCLUSTER, 'space-extend']
+            params += ['-n', six.text_type(volname)]
+            params += ['-s', six.text_type(delta)]
+            params += self._make_server_list()
+            try:
+                self._execute(*params, run_as_root=True)
+            except processutils.ProcessExecutionError as err:
+                msg = _("Error in space-extend for volume %(space)s with "
+                        "%(size)d additional GB") % {'space': volname,
+                                                     'size': delta}
+                self._handle_blocked(err, msg)
+
+    def initialize_connection(self, volume, connector):
+        """Return connection information.
+
+        Need to return noremovehost so that the Nova host
+        doesn't accidentally remove us from the apphost list if it is
+        running on the same host (like in devstack testing).
+        """
+        hgst_properties = {'name': volume['provider_id'],
+                           'noremovehost': self._hostname()}
+        return {'driver_volume_type': 'hgst',
+                'data': hgst_properties}
+
+    def local_path(self, volume):
+        """Query the provider_id to figure out the proper devnode."""
+        return "/dev/" + self._get_space_name(volume)
+
+    def create_export(self, context, volume):
+        # Not needed for spaces
+        pass
+
+    def remove_export(self, context, volume):
+        # Not needed for spaces
+        pass
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        # Not needed for spaces
+        pass
+
+    def ensure_export(self, context, volume):
+        # Not needed for spaces
+        pass
diff --git a/etc/cinder/rootwrap.d/volume.filters b/etc/cinder/rootwrap.d/volume.filters
index a00cdcc4d5e..6d38a187d28 100644
--- a/etc/cinder/rootwrap.d/volume.filters
+++ b/etc/cinder/rootwrap.d/volume.filters
@@ -188,3 +188,6 @@ aureplicationmon: EnvFilter, env, root, LANG=, STONAVM_HOME=, LD_LIBRARY_PATH=,
 
 # cinder/volume/drivers/tintri.py
 mv: CommandFilter, mv, root
+
+# cinder/volume/drivers/hgst.py
+vgc-cluster: CommandFilter, vgc-cluster, root