Added API to manage volume types
Partially-implements bp volume-type-support Change-Id: I1a58cf82c659b49e5258a563788cb8e5b05bfb8a
This commit is contained in:
parent
9dfc2d22a6
commit
dd4cbb9096
|
@ -0,0 +1,100 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
from tempest.api.share import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import exceptions
|
||||
from tempest import test
|
||||
|
||||
|
||||
class VolumeTypesAdminTest(base.BaseSharesAdminTest):
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_volume_type_create_delete(self):
|
||||
name = data_utils.rand_name("tempest-manila")
|
||||
|
||||
# Create volume type
|
||||
resp, vt_create = self.shares_client.create_volume_type(name)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(name, vt_create['name'])
|
||||
|
||||
# Delete volume type
|
||||
resp, __ = self.shares_client.delete_volume_type(vt_create['id'])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
|
||||
# Verify deletion of volume type
|
||||
self.shares_client.wait_for_resource_deletion(vt_id=vt_create['id'])
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.get_volume_type,
|
||||
vt_create['id'])
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_volume_type_create_get(self):
|
||||
name = data_utils.rand_name("tempest-manila")
|
||||
extra_specs = {"key": "value", }
|
||||
|
||||
# Create volume type
|
||||
resp, vt_create = self.create_volume_type(name,
|
||||
extra_specs=extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(name, vt_create['name'])
|
||||
|
||||
# Get volume type
|
||||
resp, get = self.shares_client.get_volume_type(vt_create['id'])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(name, get["name"])
|
||||
self.assertEqual(vt_create["id"], get["id"])
|
||||
self.assertEqual(extra_specs, get["extra_specs"])
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_volume_type_create_list(self):
|
||||
name = data_utils.rand_name("tempest-manila")
|
||||
|
||||
# Create volume type
|
||||
resp, vt_create = self.create_volume_type(name)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
|
||||
# list volume types
|
||||
resp, vt_list = self.shares_client.list_volume_types()
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertTrue(len(vt_list) >= 1)
|
||||
self.assertTrue(any(vt_create["id"] in vt["id"] for vt in vt_list))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_get_share_with_volume_type(self):
|
||||
|
||||
# Data
|
||||
share_name = data_utils.rand_name("share")
|
||||
vol_type_name = data_utils.rand_name("volume-type")
|
||||
extra_specs = {"key": "value", }
|
||||
|
||||
# Create volume type
|
||||
resp, vt_create = self.create_volume_type(vol_type_name,
|
||||
extra_specs=extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
|
||||
# Create share with volume type
|
||||
resp, share = self.create_share_wait_for_active(
|
||||
name=share_name, volume_type_id=vt_create["id"])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(share["name"], share_name)
|
||||
self.shares_client.wait_for_share_status(share["id"], "available")
|
||||
|
||||
# Verify share info
|
||||
resp, get = self.shares_client.get_share(share["id"])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(share_name, get["name"])
|
||||
self.assertEqual(share["id"], get["id"])
|
||||
self.assertEqual(vol_type_name, get["volume_type"])
|
|
@ -0,0 +1,145 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
from tempest.api.share import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import test
|
||||
|
||||
|
||||
class ExtraSpecsAdminTest(base.BaseSharesAdminTest):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(ExtraSpecsAdminTest, cls).setUpClass()
|
||||
vol_type_name = data_utils.rand_name("volume-type")
|
||||
__, cls.volume_type = cls.create_volume_type(vol_type_name)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_volume_type_extra_specs_list(self):
|
||||
extra_specs = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
resp, es_create = self.shares_client.create_volume_type_extra_specs(
|
||||
self.volume_type["id"], extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_create)
|
||||
|
||||
resp, es_list = self.shares_client.list_volume_types_extra_specs(
|
||||
self.volume_type["id"])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_list)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_update_one_volume_type_extra_spec(self):
|
||||
extra_specs = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
# Create extra specs for volume type
|
||||
resp, es_create = self.shares_client.create_volume_type_extra_specs(
|
||||
self.volume_type['id'], extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_create)
|
||||
|
||||
# Update extra specs of volume type
|
||||
extra_specs["key1"] = "fake_value1_updated"
|
||||
resp, update_one = self.shares_client.update_volume_type_extra_spec(
|
||||
self.volume_type["id"], "key1", extra_specs["key1"])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual({"key1": extra_specs["key1"]}, update_one)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_update_all_volume_type_extra_specs(self):
|
||||
extra_specs = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
# Create extra specs for volume type
|
||||
resp, es_create = self.shares_client.create_volume_type_extra_specs(
|
||||
self.volume_type['id'], extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_create)
|
||||
|
||||
# Update extra specs of volume type
|
||||
extra_specs["key2"] = "value2_updated"
|
||||
resp, update_all = self.shares_client.update_volume_type_extra_specs(
|
||||
self.volume_type["id"], extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, update_all)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_get_all_volume_type_extra_specs(self):
|
||||
extra_specs = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
# Create extra specs for volume type
|
||||
resp, es_create = self.shares_client.create_volume_type_extra_specs(
|
||||
self.volume_type['id'], extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_create)
|
||||
|
||||
# Get all extra specs for volume type
|
||||
resp, es_get_all = self.shares_client.get_volume_type_extra_specs(
|
||||
self.volume_type["id"])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_get_all)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_get_one_volume_type_extra_spec(self):
|
||||
extra_specs = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
# Create extra specs for volume type
|
||||
resp, es_create = self.shares_client.create_volume_type_extra_specs(
|
||||
self.volume_type['id'], extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_create)
|
||||
|
||||
# Get one extra spec for volume type
|
||||
resp, es_get_one = self.shares_client.get_volume_type_extra_spec(
|
||||
self.volume_type["id"], "key1")
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual({"key1": "value1", }, es_get_one)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_delete_one_volume_type_extra_spec(self):
|
||||
extra_specs = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
# Create extra specs for volume type
|
||||
resp, es_create = self.shares_client.create_volume_type_extra_specs(
|
||||
self.volume_type['id'], extra_specs)
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual(extra_specs, es_create)
|
||||
|
||||
# Delete one extra spec for volume type
|
||||
resp, __ = self.shares_client.delete_volume_type_extra_spec(
|
||||
self.volume_type["id"], "key1")
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
|
||||
# Get all extra specs for volume type
|
||||
resp, es_get_all = self.shares_client.get_volume_type_extra_specs(
|
||||
self.volume_type["id"])
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertEqual({"key2": "value2", }, es_get_all)
|
|
@ -0,0 +1,233 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
from tempest.api.share import base
|
||||
from tempest import clients_share as clients
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import exceptions
|
||||
from tempest import test
|
||||
|
||||
|
||||
class ExtraSpecsAdminNegativeTest(base.BaseSharesAdminTest):
|
||||
|
||||
def _create_volume_type(self):
|
||||
name = data_utils.rand_name("unique_vt_name")
|
||||
extra_specs = {"key": "value", }
|
||||
__, vt = self.create_volume_type(name, extra_specs=extra_specs)
|
||||
return vt
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(ExtraSpecsAdminNegativeTest, cls).setUpClass()
|
||||
cls.member_shares_client = clients.Manager().shares_client
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_create_extra_specs_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.create_volume_type_extra_specs,
|
||||
vt["id"], {"key": "new_value"})
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_list_extra_specs_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.list_volume_types_extra_specs, vt["id"])
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_get_extra_spec_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.get_volume_type_extra_spec,
|
||||
vt["id"], "key")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_get_extra_specs_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.get_volume_type_extra_specs, vt["id"])
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_update_extra_spec_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.update_volume_type_extra_spec,
|
||||
vt["id"], "key", "new_value")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_update_extra_specs_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.update_volume_type_extra_specs,
|
||||
vt["id"], {"key": "new_value"})
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_delete_extra_specs_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.delete_volume_type_extra_spec,
|
||||
vt["id"], "key")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_set_too_long_key(self):
|
||||
too_big_key = "k" * 256
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.shares_client.create_volume_type_extra_specs,
|
||||
vt["id"], {too_big_key: "value"})
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_set_too_long_value_with_creation(self):
|
||||
too_big_value = "v" * 256
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.shares_client.create_volume_type_extra_specs,
|
||||
vt["id"], {"key": too_big_value})
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_set_too_long_value_with_update(self):
|
||||
too_big_value = "v" * 256
|
||||
vt = self._create_volume_type()
|
||||
resp, body = self.shares_client.create_volume_type_extra_specs(
|
||||
vt["id"], {"key": "value"})
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.shares_client.update_volume_type_extra_specs,
|
||||
vt["id"], {"key": too_big_value})
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_set_too_long_value_with_update_of_one_key(self):
|
||||
too_big_value = "v" * 256
|
||||
vt = self._create_volume_type()
|
||||
resp, body = self.shares_client.create_volume_type_extra_specs(
|
||||
vt["id"], {"key": "value"})
|
||||
self.assertIn(int(resp["status"]), test.HTTP_SUCCESS)
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.shares_client.update_volume_type_extra_spec,
|
||||
vt["id"], "key", too_big_value)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_list_es_with_empty_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.list_volume_types_extra_specs, "")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_list_es_with_invalid_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.list_volume_types_extra_specs,
|
||||
data_utils.rand_name("fake"))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_create_es_with_empty_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.create_volume_type_extra_specs,
|
||||
"", {"key1": "value1", })
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_create_es_with_invalid_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.create_volume_type_extra_specs,
|
||||
data_utils.rand_name("fake"), {"key1": "value1", })
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_create_es_with_empty_specs(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.shares_client.create_volume_type_extra_specs,
|
||||
vt["id"], "")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_create_es_with_invalid_specs(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.shares_client.create_volume_type_extra_specs,
|
||||
vt["id"], {"": "value_with_empty_key"})
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_get_extra_spec_with_empty_key(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.get_volume_type_extra_spec,
|
||||
vt["id"], "")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_get_extra_spec_with_invalid_key(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.get_volume_type_extra_spec,
|
||||
vt["id"], data_utils.rand_name("fake"))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_get_extra_specs_with_empty_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.get_volume_type_extra_specs,
|
||||
"")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_get_extra_specs_with_invalid_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.get_volume_type_extra_specs,
|
||||
data_utils.rand_name("fake"))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_delete_es_key_with_empty_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.delete_volume_type_extra_spec,
|
||||
"", "key", )
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_delete_es_key_with_invalid_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.delete_volume_type_extra_spec,
|
||||
data_utils.rand_name("fake"), "key", )
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_delete_with_invalid_key(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.delete_volume_type_extra_spec,
|
||||
vt["id"], data_utils.rand_name("fake"))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_update_spec_with_empty_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.update_volume_type_extra_spec,
|
||||
"", "key", "new_value")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_update_spec_with_invalid_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.update_volume_type_extra_spec,
|
||||
data_utils.rand_name("fake"), "key", "new_value")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_update_spec_with_empty_key(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.update_volume_type_extra_spec,
|
||||
vt["id"], "", "new_value")
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_update_with_invalid_vol_type_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.update_volume_type_extra_specs,
|
||||
data_utils.rand_name("fake"), {"key": "new_value"})
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_update_with_invalid_specs(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.shares_client.update_volume_type_extra_specs,
|
||||
vt["id"], {"": "new_value"})
|
|
@ -0,0 +1,83 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
from tempest.api.share import base
|
||||
from tempest import clients_share as clients
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import exceptions
|
||||
from tempest import test
|
||||
|
||||
|
||||
class VolumeTypesAdminNegativeTest(base.BaseSharesAdminTest):
|
||||
|
||||
def _create_volume_type(self):
|
||||
name = data_utils.rand_name("unique_vt_name")
|
||||
extra_specs = {"key": "value", }
|
||||
__, vt = self.create_volume_type(name, extra_specs=extra_specs)
|
||||
return vt
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(VolumeTypesAdminNegativeTest, cls).setUpClass()
|
||||
cls.member_shares_client = clients.Manager().shares_client
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_create_volume_type_with_user(self):
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.create_volume_type,
|
||||
data_utils.rand_name("used_user_creds"),
|
||||
client=self.member_shares_client)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_delete_volume_type_with_user(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Unauthorized,
|
||||
self.member_shares_client.delete_volume_type,
|
||||
vt["id"])
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_create_share_with_nonexistent_volume_type(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.create_share_wait_for_active,
|
||||
volume_type_id=data_utils.rand_name("fake"))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_create_volume_type_with_empty_name(self):
|
||||
self.assertRaises(exceptions.BadRequest, self.create_volume_type, '')
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_create_volume_type_with_too_big_name(self):
|
||||
self.assertRaises(exceptions.BadRequest,
|
||||
self.create_volume_type,
|
||||
"x" * 256)
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_get_volume_type_by_nonexistent_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.get_volume_type,
|
||||
data_utils.rand_name("fake"))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_delete_volume_type_by_nonexistent_id(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.shares_client.delete_volume_type,
|
||||
data_utils.rand_name("fake"))
|
||||
|
||||
@test.attr(type=["gate", "smoke", ])
|
||||
def test_try_create_duplicate_of_volume_type(self):
|
||||
vt = self._create_volume_type()
|
||||
self.assertRaises(exceptions.Conflict,
|
||||
self.create_volume_type,
|
||||
vt["name"])
|
|
@ -231,7 +231,9 @@ class BaseSharesTest(test.BaseTestCase):
|
|||
def create_share_wait_for_active(cls, share_protocol=None, size=1,
|
||||
name=None, snapshot_id=None,
|
||||
description=None, metadata={},
|
||||
share_network_id=None, client=None,
|
||||
share_network_id=None,
|
||||
volume_type_id=None,
|
||||
client=None,
|
||||
cleanup_in_class=True):
|
||||
if client is None:
|
||||
client = cls.shares_client
|
||||
|
@ -249,7 +251,8 @@ class BaseSharesTest(test.BaseTestCase):
|
|||
name=name, snapshot_id=snapshot_id,
|
||||
description=description,
|
||||
metadata=metadata,
|
||||
share_network_id=share_network_id)
|
||||
share_network_id=share_network_id,
|
||||
volume_type_id=volume_type_id, )
|
||||
resource = {
|
||||
"type": "share",
|
||||
"id": s["id"],
|
||||
|
@ -317,6 +320,23 @@ class BaseSharesTest(test.BaseTestCase):
|
|||
cls.method_resources.insert(0, resource)
|
||||
return resp, ss
|
||||
|
||||
@classmethod
|
||||
def create_volume_type(cls, name, client=None, cleanup_in_class=True,
|
||||
**kwargs):
|
||||
if client is None:
|
||||
client = cls.shares_client
|
||||
resp, vt = client.create_volume_type(name, **kwargs)
|
||||
resource = {
|
||||
"type": "volume_type",
|
||||
"id": vt["id"],
|
||||
"client": client,
|
||||
}
|
||||
if cleanup_in_class:
|
||||
cls.class_resources.insert(0, resource)
|
||||
else:
|
||||
cls.method_resources.insert(0, resource)
|
||||
return resp, vt
|
||||
|
||||
@classmethod
|
||||
def clear_isolated_creds(cls, creds=None):
|
||||
if creds is None:
|
||||
|
@ -368,6 +388,9 @@ class BaseSharesTest(test.BaseTestCase):
|
|||
elif res["type"] is "security_service":
|
||||
client.delete_security_service(res_id)
|
||||
client.wait_for_resource_deletion(ss_id=res_id)
|
||||
elif res["type"] is "volume_type":
|
||||
client.delete_volume_type(res_id)
|
||||
client.wait_for_resource_deletion(vt_id=res_id)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
except exceptions.Unauthorized:
|
||||
|
|
|
@ -49,7 +49,8 @@ class SharesClient(rest_client.RestClient):
|
|||
|
||||
def create_share(self, share_protocol=None, size=1,
|
||||
name=None, snapshot_id=None, description=None,
|
||||
metadata={}, share_network_id=None):
|
||||
metadata={}, share_network_id=None,
|
||||
volume_type_id=None, ):
|
||||
if name is None:
|
||||
name = rand_name("tempest-created-share")
|
||||
if description is None:
|
||||
|
@ -70,6 +71,8 @@ class SharesClient(rest_client.RestClient):
|
|||
}
|
||||
if share_network_id:
|
||||
post_body["share"]["share_network_id"] = share_network_id
|
||||
if volume_type_id:
|
||||
post_body["share"]["volume_type"] = volume_type_id
|
||||
body = json.dumps(post_body)
|
||||
resp, body = self.post("shares", body)
|
||||
return resp, self._parse_resp(body)
|
||||
|
@ -267,7 +270,7 @@ class SharesClient(rest_client.RestClient):
|
|||
"""Verifies deleted resource or not.
|
||||
|
||||
:param kwargs: expected keys are 'share_id', 'rule_id',
|
||||
:param kwargs: 'snapshot_id', 'sn_id', 'ss_id'
|
||||
:param kwargs: 'snapshot_id', 'sn_id', 'ss_id' and 'vt_id'
|
||||
:raises share_exceptions.InvalidResource
|
||||
"""
|
||||
|
||||
|
@ -307,6 +310,11 @@ class SharesClient(rest_client.RestClient):
|
|||
self.get_security_service(kwargs.get("sn_id"))
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
elif "vt_id" in kwargs:
|
||||
try:
|
||||
self.get_volume_type(kwargs.get("vt_id"))
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
else:
|
||||
raise share_exceptions.InvalidResource(message=str(kwargs))
|
||||
return False
|
||||
|
@ -529,3 +537,72 @@ class SharesClient(rest_client.RestClient):
|
|||
def list_sec_services_for_share_network(self, sn_id):
|
||||
resp, body = self.get("security-services?share_network_id=%s" % sn_id)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
###############
|
||||
|
||||
def list_volume_types(self, params=None):
|
||||
uri = 'types'
|
||||
if params is not None:
|
||||
uri += '?%s' % urllib.urlencode(params)
|
||||
resp, body = self.get(uri)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def create_volume_type(self, name, **kwargs):
|
||||
post_body = {
|
||||
'name': name,
|
||||
'extra_specs': kwargs.get('extra_specs'),
|
||||
}
|
||||
post_body = json.dumps({'volume_type': post_body})
|
||||
resp, body = self.post('types', post_body)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def delete_volume_type(self, vol_type_id):
|
||||
return self.delete("types/%s" % vol_type_id)
|
||||
|
||||
def get_volume_type(self, vol_type_id):
|
||||
resp, body = self.get("types/%s" % vol_type_id)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
###############
|
||||
|
||||
def list_volume_types_extra_specs(self, vol_type_id, params=None):
|
||||
uri = 'types/%s/extra_specs' % vol_type_id
|
||||
if params is not None:
|
||||
uri += '?%s' % urllib.urlencode(params)
|
||||
resp, body = self.get(uri)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def create_volume_type_extra_specs(self, vol_type_id, extra_specs):
|
||||
url = "types/%s/extra_specs" % vol_type_id
|
||||
post_body = json.dumps({'extra_specs': extra_specs})
|
||||
resp, body = self.post(url, post_body)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def get_volume_type_extra_spec(self, vol_type_id, extra_spec_name):
|
||||
uri = "types/%s/extra_specs/%s" % (vol_type_id, extra_spec_name)
|
||||
resp, body = self.get(uri)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def get_volume_type_extra_specs(self, vol_type_id):
|
||||
uri = "types/%s/extra_specs" % vol_type_id
|
||||
resp, body = self.get(uri)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def update_volume_type_extra_spec(self, vol_type_id, spec_name,
|
||||
spec_value):
|
||||
uri = "types/%s/extra_specs/%s" % (vol_type_id, spec_name)
|
||||
extra_spec = {spec_name: spec_value}
|
||||
post_body = json.dumps(extra_spec)
|
||||
resp, body = self.put(uri, post_body)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def update_volume_type_extra_specs(self, vol_type_id, extra_specs):
|
||||
uri = "types/%s/extra_specs" % vol_type_id
|
||||
extra_specs = {"extra_specs": extra_specs}
|
||||
post_body = json.dumps(extra_specs)
|
||||
resp, body = self.post(uri, post_body)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def delete_volume_type_extra_spec(self, vol_type_id, extra_spec_name):
|
||||
uri = "types/%s/extra_specs/%s" % (vol_type_id, extra_spec_name)
|
||||
return self.delete(uri)
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
|
||||
"share_extension:services": [["rule:admin_api"]],
|
||||
|
||||
"share_extension:types_manage": [["rule:admin_api"]],
|
||||
"share_extension:types_extra_specs": [["rule:admin_api"]],
|
||||
|
||||
"security_service:create": [],
|
||||
"security_service:delete": [],
|
||||
"security_service:update": [],
|
||||
|
|
|
@ -35,6 +35,22 @@ CONF = cfg.CONF
|
|||
XML_NS_V1 = 'http://docs.openstack.org/volume/api/v1'
|
||||
|
||||
|
||||
# Regex that matches alphanumeric characters, periods, hypens,
|
||||
# colons and underscores:
|
||||
# ^ assert position at start of the string
|
||||
# [\w\.\-\:\_] match expression
|
||||
# $ assert position at end of the string
|
||||
VALID_KEY_NAME_REGEX = re.compile(r"^[\w\.\-\:\_]+$", re.UNICODE)
|
||||
|
||||
|
||||
def validate_key_names(key_names_list):
|
||||
"""Validate each item of the list to match key name regex."""
|
||||
for key_name in key_names_list:
|
||||
if not VALID_KEY_NAME_REGEX.match(key_name):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_pagination_params(request):
|
||||
"""Return marker, limit tuple from request.
|
||||
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
# Copyright (c) 2011 Zadara Storage Inc.
|
||||
# Copyright (c) 2011 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""The volume types extra specs extension"""
|
||||
|
||||
import webob
|
||||
|
||||
from manila.api import common
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila.openstack.common.notifier import api as notifier_api
|
||||
from manila.share import volume_types
|
||||
|
||||
authorize = extensions.extension_authorizer('share', 'types_extra_specs')
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||
"""The volume type extra specs API controller for the OpenStack API."""
|
||||
|
||||
def _get_extra_specs(self, context, type_id):
|
||||
extra_specs = db.volume_type_extra_specs_get(context, type_id)
|
||||
specs_dict = {}
|
||||
for key, value in extra_specs.iteritems():
|
||||
specs_dict[key] = value
|
||||
return dict(extra_specs=specs_dict)
|
||||
|
||||
def _check_type(self, context, type_id):
|
||||
try:
|
||||
volume_types.get_volume_type(context, type_id)
|
||||
except exception.NotFound as ex:
|
||||
raise webob.exc.HTTPNotFound(explanation=ex.msg)
|
||||
|
||||
def _verify_extra_specs(self, extra_specs):
|
||||
# keys and values in extra_specs can be only strings
|
||||
# with length in range(1, 256)
|
||||
is_valid = True
|
||||
for k, v in extra_specs.iteritems():
|
||||
if not (isinstance(k, basestring) and len(k) in range(1, 256)):
|
||||
is_valid = False
|
||||
break
|
||||
if isinstance(v, dict):
|
||||
self._verify_extra_specs(v)
|
||||
elif isinstance(v, basestring):
|
||||
if len(v) not in range(1, 256):
|
||||
is_valid = False
|
||||
break
|
||||
else:
|
||||
is_valid = False
|
||||
break
|
||||
if not is_valid:
|
||||
expl = _('Invalid request body')
|
||||
raise webob.exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
def index(self, req, type_id):
|
||||
"""Returns the list of extra specs for a given volume type."""
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
self._check_type(context, type_id)
|
||||
return self._get_extra_specs(context, type_id)
|
||||
|
||||
def create(self, req, type_id, body=None):
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
|
||||
if not self.is_valid_body(body, 'extra_specs'):
|
||||
raise webob.exc.HTTPBadRequest()
|
||||
|
||||
self._check_type(context, type_id)
|
||||
self._verify_extra_specs(body)
|
||||
specs = body['extra_specs']
|
||||
self._check_key_names(specs.keys())
|
||||
db.volume_type_extra_specs_update_or_create(context,
|
||||
type_id,
|
||||
specs)
|
||||
notifier_info = dict(type_id=type_id, specs=specs)
|
||||
notifier_api.notify(context, 'volumeTypeExtraSpecs',
|
||||
'volume_type_extra_specs.create',
|
||||
notifier_api.INFO, notifier_info)
|
||||
return body
|
||||
|
||||
def update(self, req, type_id, id, body=None):
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
if not body:
|
||||
expl = _('Request body empty')
|
||||
raise webob.exc.HTTPBadRequest(explanation=expl)
|
||||
self._check_type(context, type_id)
|
||||
if id not in body:
|
||||
expl = _('Request body and URI mismatch')
|
||||
raise webob.exc.HTTPBadRequest(explanation=expl)
|
||||
if len(body) > 1:
|
||||
expl = _('Request body contains too many items')
|
||||
raise webob.exc.HTTPBadRequest(explanation=expl)
|
||||
self._verify_extra_specs(body)
|
||||
db.volume_type_extra_specs_update_or_create(context,
|
||||
type_id,
|
||||
body)
|
||||
notifier_info = dict(type_id=type_id, id=id)
|
||||
notifier_api.notify(context, 'volumeTypeExtraSpecs',
|
||||
'volume_type_extra_specs.update',
|
||||
notifier_api.INFO, notifier_info)
|
||||
return body
|
||||
|
||||
def show(self, req, type_id, id):
|
||||
"""Return a single extra spec item."""
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
self._check_type(context, type_id)
|
||||
specs = self._get_extra_specs(context, type_id)
|
||||
if id in specs['extra_specs']:
|
||||
return {id: specs['extra_specs'][id]}
|
||||
else:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
def delete(self, req, type_id, id):
|
||||
"""Deletes an existing extra spec."""
|
||||
context = req.environ['manila.context']
|
||||
self._check_type(context, type_id)
|
||||
authorize(context)
|
||||
|
||||
try:
|
||||
db.volume_type_extra_specs_delete(context, type_id, id)
|
||||
except exception.VolumeTypeExtraSpecsNotFound as error:
|
||||
raise webob.exc.HTTPNotFound(explanation=error.msg)
|
||||
|
||||
notifier_info = dict(type_id=type_id, id=id)
|
||||
notifier_api.notify(context, 'volumeTypeExtraSpecs',
|
||||
'volume_type_extra_specs.delete',
|
||||
notifier_api.INFO, notifier_info)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
def _check_key_names(self, keys):
|
||||
if not common.validate_key_names(keys):
|
||||
expl = _('Key names can only contain alphanumeric characters, '
|
||||
'underscores, periods, colons and hyphens.')
|
||||
|
||||
raise webob.exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
|
||||
class Types_extra_specs(extensions.ExtensionDescriptor):
|
||||
"""Type extra specs support."""
|
||||
|
||||
name = "TypesExtraSpecs"
|
||||
alias = "os-types-extra-specs"
|
||||
namespace = "http://docs.openstack.org/share/ext/types-extra-specs/api/v1"
|
||||
updated = "2011-08-24T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
res = extensions.ResourceExtension(
|
||||
'extra_specs',
|
||||
VolumeTypeExtraSpecsController(),
|
||||
parent=dict(member_name='type',
|
||||
collection_name='types')
|
||||
)
|
||||
resources.append(res)
|
||||
|
||||
return resources
|
|
@ -0,0 +1,124 @@
|
|||
# Copyright (c) 2011 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""The volume types manage extension."""
|
||||
|
||||
import webob
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.views import types as views_types
|
||||
from manila import exception
|
||||
from manila.openstack.common.notifier import api as notifier_api
|
||||
from manila.share import volume_types
|
||||
|
||||
|
||||
authorize = extensions.extension_authorizer('share', 'types_manage')
|
||||
|
||||
|
||||
class VolumeTypesManageController(wsgi.Controller):
|
||||
"""The volume types API controller for the OpenStack API."""
|
||||
|
||||
_view_builder_class = views_types.ViewBuilder
|
||||
|
||||
def _notify_volume_type_error(self, context, method, payload):
|
||||
notifier_api.notify(context,
|
||||
'volumeType',
|
||||
method,
|
||||
notifier_api.ERROR,
|
||||
payload)
|
||||
|
||||
@wsgi.action("create")
|
||||
def _create(self, req, body):
|
||||
"""Creates a new volume type."""
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
|
||||
if not self.is_valid_body(body, 'volume_type'):
|
||||
raise webob.exc.HTTPBadRequest()
|
||||
|
||||
vol_type = body['volume_type']
|
||||
name = vol_type.get('name', None)
|
||||
specs = vol_type.get('extra_specs', {})
|
||||
|
||||
if name is None or name == "" or len(name) > 255:
|
||||
raise webob.exc.HTTPBadRequest()
|
||||
|
||||
try:
|
||||
volume_types.create(context, name, specs)
|
||||
vol_type = volume_types.get_volume_type_by_name(context, name)
|
||||
notifier_info = dict(volume_types=vol_type)
|
||||
notifier_api.notify(context, 'volumeType',
|
||||
'volume_type.create',
|
||||
notifier_api.INFO, notifier_info)
|
||||
|
||||
except exception.VolumeTypeExists as err:
|
||||
notifier_err = dict(volume_types=vol_type, error_message=str(err))
|
||||
self._notify_volume_type_error(context,
|
||||
'volume_type.create',
|
||||
notifier_err)
|
||||
|
||||
raise webob.exc.HTTPConflict(explanation=str(err))
|
||||
except exception.NotFound as err:
|
||||
notifier_err = dict(volume_types=vol_type, error_message=str(err))
|
||||
self._notify_volume_type_error(context,
|
||||
'volume_type.create',
|
||||
notifier_err)
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
return self._view_builder.show(req, vol_type)
|
||||
|
||||
@wsgi.action("delete")
|
||||
def _delete(self, req, id):
|
||||
"""Deletes an existing volume type."""
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
|
||||
try:
|
||||
vol_type = volume_types.get_volume_type(context, id)
|
||||
volume_types.destroy(context, vol_type['id'])
|
||||
notifier_info = dict(volume_types=vol_type)
|
||||
notifier_api.notify(context, 'volumeType',
|
||||
'volume_type.delete',
|
||||
notifier_api.INFO, notifier_info)
|
||||
except exception.VolumeTypeInUse as err:
|
||||
notifier_err = dict(id=id, error_message=str(err))
|
||||
self._notify_volume_type_error(context,
|
||||
'volume_type.delete',
|
||||
notifier_err)
|
||||
msg = 'Target volume type is still in use.'
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
except exception.NotFound as err:
|
||||
notifier_err = dict(id=id, error_message=str(err))
|
||||
self._notify_volume_type_error(context,
|
||||
'volume_type.delete',
|
||||
notifier_err)
|
||||
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
|
||||
class Types_manage(extensions.ExtensionDescriptor):
|
||||
"""Types manage support."""
|
||||
|
||||
name = "TypesManage"
|
||||
alias = "os-types-manage"
|
||||
namespace = "http://docs.openstack.org/share/ext/types-manage/api/v1"
|
||||
updated = "2011-08-24T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeTypesManageController()
|
||||
extension = extensions.ControllerExtension(self, 'types', controller)
|
||||
return [extension]
|
|
@ -31,6 +31,7 @@ from manila.api.v1 import share_metadata
|
|||
from manila.api.v1 import share_networks
|
||||
from manila.api.v1 import share_snapshots
|
||||
from manila.api.v1 import shares
|
||||
from manila.api.v1 import volume_types
|
||||
|
||||
from manila.openstack.common import log as logging
|
||||
|
||||
|
@ -95,3 +96,9 @@ class APIRouter(manila.api.openstack.APIRouter):
|
|||
controller=self.resources['share_networks'],
|
||||
collection={'detail': 'GET'},
|
||||
member={'action': 'POST'})
|
||||
|
||||
self.resources['types'] = volume_types.create_resource()
|
||||
mapper.resource("type", "types",
|
||||
controller=self.resources['types'],
|
||||
collection={'detail': 'GET'},
|
||||
member={'action': 'POST'})
|
||||
|
|
|
@ -25,7 +25,9 @@ from manila.api import xmlutil
|
|||
from manila.common import constants
|
||||
from manila import exception
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import uuidutils
|
||||
from manila import share
|
||||
from manila.share import volume_types
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -217,6 +219,21 @@ class ShareController(wsgi.Controller):
|
|||
|
||||
display_name = share.get('display_name')
|
||||
display_description = share.get('display_description')
|
||||
|
||||
req_volume_type = share.get('volume_type', None)
|
||||
if req_volume_type:
|
||||
try:
|
||||
if not uuidutils.is_uuid_like(req_volume_type):
|
||||
kwargs['volume_type'] = \
|
||||
volume_types.get_volume_type_by_name(
|
||||
context, req_volume_type)
|
||||
else:
|
||||
kwargs['volume_type'] = volume_types.get_volume_type(
|
||||
context, req_volume_type)
|
||||
except exception.VolumeTypeNotFound:
|
||||
msg = _("Volume type not found.")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
new_share = self.share_api.create(context,
|
||||
share_proto,
|
||||
size,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# Copyright (c) 2014 NetApp, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""The volume type & volume types extra specs extension."""
|
||||
|
||||
from webob import exc
|
||||
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.views import types as views_types
|
||||
from manila import exception
|
||||
from manila.share import volume_types
|
||||
|
||||
|
||||
class VolumeTypesController(wsgi.Controller):
|
||||
"""The volume types API controller for the OpenStack API."""
|
||||
|
||||
_view_builder_class = views_types.ViewBuilder
|
||||
|
||||
def index(self, req):
|
||||
"""Returns the list of volume types."""
|
||||
context = req.environ['manila.context']
|
||||
vol_types = volume_types.get_all_types(context).values()
|
||||
return self._view_builder.index(req, vol_types)
|
||||
|
||||
def show(self, req, id):
|
||||
"""Return a single volume type item."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
try:
|
||||
vol_type = volume_types.get_volume_type(context, id)
|
||||
except exception.NotFound:
|
||||
msg = _("Volume type not found")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
vol_type['id'] = str(vol_type['id'])
|
||||
return self._view_builder.show(req, vol_type)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(VolumeTypesController())
|
|
@ -49,6 +49,10 @@ class ViewBuilder(common.ViewBuilder):
|
|||
metadata = dict((item['key'], item['value']) for item in metadata)
|
||||
else:
|
||||
metadata = {}
|
||||
if share['volume_type_id'] and share.get('volume_type'):
|
||||
volume_type = share['volume_type']['name']
|
||||
else:
|
||||
volume_type = share['volume_type_id']
|
||||
|
||||
return {
|
||||
'share': {
|
||||
|
@ -66,6 +70,7 @@ class ViewBuilder(common.ViewBuilder):
|
|||
'share_proto': share.get('share_proto'),
|
||||
'export_location': share.get('export_location'),
|
||||
'metadata': metadata,
|
||||
'volume_type': volume_type,
|
||||
'links': self._get_links(request, share['id'])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright 2012 Openstack Foundation.
|
||||
# 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.
|
||||
|
||||
from manila.api import common
|
||||
|
||||
|
||||
class ViewBuilder(common.ViewBuilder):
|
||||
|
||||
_collection_name = 'types'
|
||||
|
||||
def show(self, request, volume_type, brief=False):
|
||||
"""Trim away extraneous volume type attributes."""
|
||||
trimmed = dict(id=volume_type.get('id'),
|
||||
name=volume_type.get('name'),
|
||||
extra_specs=volume_type.get('extra_specs'))
|
||||
return trimmed if brief else dict(volume_type=trimmed)
|
||||
|
||||
def index(self, request, volume_types):
|
||||
"""Index over trimmed volume types."""
|
||||
volume_types_list = [self.show(request, volume_type, True)
|
||||
for volume_type in volume_types]
|
||||
return dict(volume_types=volume_types_list)
|
|
@ -115,8 +115,8 @@ class FilterScheduler(driver.Scheduler):
|
|||
# takes 'resource_XX' and 'volume_XX' as input respectively, copying
|
||||
# 'volume_XX' to 'resource_XX' will make both filters happy.
|
||||
resource_properties = share_properties.copy()
|
||||
share_type = request_spec.get("share_type", {})
|
||||
resource_type = request_spec.get("share_type", {})
|
||||
volume_type = request_spec.get("volume_type", {})
|
||||
resource_type = request_spec.get("volume_type", {})
|
||||
request_spec.update({'resource_properties': resource_properties})
|
||||
|
||||
config_options = self._get_configuration_options()
|
||||
|
@ -128,7 +128,7 @@ class FilterScheduler(driver.Scheduler):
|
|||
filter_properties.update({'context': context,
|
||||
'request_spec': request_spec,
|
||||
'config_options': config_options,
|
||||
'share_type': share_type,
|
||||
'volume_type': volume_type,
|
||||
'resource_type': resource_type
|
||||
})
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class API(base.Base):
|
|||
|
||||
def create(self, context, share_proto, size, name, description,
|
||||
snapshot=None, availability_zone=None, metadata=None,
|
||||
share_network_id=None):
|
||||
share_network_id=None, volume_type=None):
|
||||
"""Create new share."""
|
||||
policy.check_policy(context, 'share', 'create')
|
||||
|
||||
|
@ -86,6 +86,13 @@ class API(base.Base):
|
|||
"than snapshot size") % size)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
if snapshot and volume_type:
|
||||
if volume_type['id'] != snapshot['volume_type_id']:
|
||||
msg = _("Invalid volume_type provided (requested type "
|
||||
"must match source snapshot, or be omitted). "
|
||||
"You should omit the argument.")
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
#TODO(rushiagr): Find a suitable place to keep all the allowed
|
||||
# share types so that it becomes easier to add one
|
||||
if share_proto.lower() not in ['nfs', 'cifs']:
|
||||
|
@ -134,6 +141,7 @@ class API(base.Base):
|
|||
'display_name': name,
|
||||
'display_description': description,
|
||||
'share_proto': share_proto,
|
||||
'volume_type_id': volume_type['id'] if volume_type else None
|
||||
}
|
||||
|
||||
try:
|
||||
|
@ -150,6 +158,7 @@ class API(base.Base):
|
|||
'share_proto': share_proto,
|
||||
'share_id': share['id'],
|
||||
'snapshot_id': share['snapshot_id'],
|
||||
'volume_type': volume_type
|
||||
}
|
||||
|
||||
filter_properties = {}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
# Copyright (c) 2014 Openstack Foundation.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Built-in volume type properties."""
|
||||
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from manila import context
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila.openstack.common import log as logging
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create(context, name, extra_specs={}):
|
||||
"""Creates volume types."""
|
||||
try:
|
||||
type_ref = db.volume_type_create(context,
|
||||
dict(name=name,
|
||||
extra_specs=extra_specs))
|
||||
except exception.DBError as e:
|
||||
LOG.exception(_('DB error: %s') % e)
|
||||
raise exception.VolumeTypeCreateFailed(name=name,
|
||||
extra_specs=extra_specs)
|
||||
return type_ref
|
||||
|
||||
|
||||
def destroy(context, id):
|
||||
"""Marks volume types as deleted."""
|
||||
if id is None:
|
||||
msg = _("id cannot be None")
|
||||
raise exception.InvalidVolumeType(reason=msg)
|
||||
else:
|
||||
db.volume_type_destroy(context, id)
|
||||
|
||||
|
||||
def get_all_types(context, inactive=0, search_opts={}):
|
||||
"""Get all non-deleted volume_types.
|
||||
|
||||
Pass true as argument if you want deleted volume types returned also.
|
||||
"""
|
||||
vol_types = db.volume_type_get_all(context, inactive)
|
||||
|
||||
if search_opts:
|
||||
LOG.debug(_("Searching by: %s") % search_opts)
|
||||
|
||||
def _check_extra_specs_match(vol_type, searchdict):
|
||||
for k, v in searchdict.iteritems():
|
||||
if (k not in vol_type['extra_specs'].keys()
|
||||
or vol_type['extra_specs'][k] != v):
|
||||
return False
|
||||
return True
|
||||
|
||||
# search_option to filter_name mapping.
|
||||
filter_mapping = {'extra_specs': _check_extra_specs_match}
|
||||
|
||||
result = {}
|
||||
for type_name, type_args in vol_types.iteritems():
|
||||
# go over all filters in the list
|
||||
for opt, values in search_opts.iteritems():
|
||||
try:
|
||||
filter_func = filter_mapping[opt]
|
||||
except KeyError:
|
||||
# no such filter - ignore it, go to next filter
|
||||
continue
|
||||
else:
|
||||
if filter_func(type_args, values):
|
||||
result[type_name] = type_args
|
||||
break
|
||||
vol_types = result
|
||||
return vol_types
|
||||
|
||||
|
||||
def get_volume_type(ctxt, id):
|
||||
"""Retrieves single volume type by id."""
|
||||
if id is None:
|
||||
msg = _("id cannot be None")
|
||||
raise exception.InvalidVolumeType(reason=msg)
|
||||
|
||||
if ctxt is None:
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
return db.volume_type_get(ctxt, id)
|
||||
|
||||
|
||||
def get_volume_type_by_name(context, name):
|
||||
"""Retrieves single volume type by name."""
|
||||
if name is None:
|
||||
msg = _("name cannot be None")
|
||||
raise exception.InvalidVolumeType(reason=msg)
|
||||
|
||||
return db.volume_type_get_by_name(context, name)
|
||||
|
||||
|
||||
def get_default_volume_type():
|
||||
"""Get the default volume type."""
|
||||
name = CONF.default_volume_type
|
||||
vol_type = {}
|
||||
|
||||
if name is not None:
|
||||
ctxt = context.get_admin_context()
|
||||
try:
|
||||
vol_type = get_volume_type_by_name(ctxt, name)
|
||||
except exception.VolumeTypeNotFoundByName as e:
|
||||
# Couldn't find volume type with the name in default_volume_type
|
||||
# flag, record this issue and move on
|
||||
#TODO(zhiteng) consider add notification to warn admin
|
||||
LOG.exception(_('Default volume type is not found, '
|
||||
'please check default_volume_type config: %s'), e)
|
||||
|
||||
return vol_type
|
||||
|
||||
|
||||
def get_volume_type_extra_specs(volume_type_id, key=False):
|
||||
volume_type = get_volume_type(context.get_admin_context(),
|
||||
volume_type_id)
|
||||
extra_specs = volume_type['extra_specs']
|
||||
if key:
|
||||
if extra_specs.get(key):
|
||||
return extra_specs.get(key)
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return extra_specs
|
||||
|
||||
|
||||
def volume_types_diff(context, vol_type_id1, vol_type_id2):
|
||||
"""Returns a 'diff' of two volume types and whether they are equal.
|
||||
|
||||
Returns a tuple of (diff, equal), where 'equal' is a boolean indicating
|
||||
whether there is any difference, and 'diff' is a dictionary with the
|
||||
following format:
|
||||
{'extra_specs': {'key1': (value_in_1st_vol_type, value_in_2nd_vol_type),
|
||||
'key2': (value_in_1st_vol_type, value_in_2nd_vol_type),
|
||||
...}
|
||||
"""
|
||||
|
||||
def _dict_diff(dict1, dict2):
|
||||
res = {}
|
||||
equal = True
|
||||
if dict1 is None:
|
||||
dict1 = {}
|
||||
if dict2 is None:
|
||||
dict2 = {}
|
||||
for k, v in dict1.iteritems():
|
||||
res[k] = (v, dict2.get(k))
|
||||
if k not in dict2 or res[k][0] != res[k][1]:
|
||||
equal = False
|
||||
for k, v in dict2.iteritems():
|
||||
res[k] = (dict1.get(k), v)
|
||||
if k not in dict1 or res[k][0] != res[k][1]:
|
||||
equal = False
|
||||
return (res, equal)
|
||||
|
||||
all_equal = True
|
||||
diff = {}
|
||||
vol_type1 = get_volume_type(context, vol_type_id1)
|
||||
vol_type2 = get_volume_type(context, vol_type_id2)
|
||||
|
||||
extra_specs1 = vol_type1.get('extra_specs')
|
||||
extra_specs2 = vol_type2.get('extra_specs')
|
||||
diff['extra_specs'], equal = _dict_diff(extra_specs1, extra_specs2)
|
||||
if not equal:
|
||||
all_equal = False
|
||||
|
||||
return (diff, all_equal)
|
|
@ -39,6 +39,7 @@ def stub_share(id, **kwargs):
|
|||
'display_description': 'displaydesc',
|
||||
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||
'snapshot_id': '2',
|
||||
'volume_type_id': '1',
|
||||
'share_network_id': None
|
||||
}
|
||||
share.update(kwargs)
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
# Copyright (c) 2011 Zadara Storage Inc.
|
||||
# Copyright (c) 2011 OpenStack Foundation
|
||||
# Copyright 2011 University of Southern California
|
||||
# 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.
|
||||
|
||||
from lxml import etree
|
||||
import webob
|
||||
|
||||
import mock
|
||||
|
||||
from manila.api.contrib import types_extra_specs
|
||||
from manila import exception
|
||||
from manila.openstack.common.notifier import api as notifier_api
|
||||
from manila.openstack.common.notifier import test_notifier
|
||||
from manila import test
|
||||
from manila.tests.api import fakes
|
||||
import manila.wsgi
|
||||
|
||||
|
||||
def return_create_volume_type_extra_specs(context, volume_type_id,
|
||||
extra_specs):
|
||||
return stub_volume_type_extra_specs()
|
||||
|
||||
|
||||
def return_volume_type_extra_specs(context, volume_type_id):
|
||||
return stub_volume_type_extra_specs()
|
||||
|
||||
|
||||
def return_empty_volume_type_extra_specs(context, volume_type_id):
|
||||
return {}
|
||||
|
||||
|
||||
def delete_volume_type_extra_specs(context, volume_type_id, key):
|
||||
pass
|
||||
|
||||
|
||||
def delete_volume_type_extra_specs_not_found(context, volume_type_id, key):
|
||||
raise exception.VolumeTypeExtraSpecsNotFound("Not Found")
|
||||
|
||||
|
||||
def stub_volume_type_extra_specs():
|
||||
specs = {"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5"}
|
||||
return specs
|
||||
|
||||
|
||||
def volume_type_get(context, volume_type_id):
|
||||
pass
|
||||
|
||||
|
||||
class VolumeTypesExtraSpecsTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeTypesExtraSpecsTest, self).setUp()
|
||||
self.flags(host='fake',
|
||||
notification_driver=[test_notifier.__name__])
|
||||
self.stubs.Set(manila.db, 'volume_type_get', volume_type_get)
|
||||
self.api_path = '/v2/fake/os-volume-types/1/extra_specs'
|
||||
self.controller = types_extra_specs.VolumeTypeExtraSpecsController()
|
||||
"""to reset notifier drivers left over from other api/contrib tests"""
|
||||
notifier_api._reset_drivers()
|
||||
test_notifier.NOTIFICATIONS = []
|
||||
#self.addCleanup(notifier_api._reset_drivers)
|
||||
|
||||
def test_index(self):
|
||||
self.stubs.Set(manila.db, 'volume_type_extra_specs_get',
|
||||
return_volume_type_extra_specs)
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
res_dict = self.controller.index(req, 1)
|
||||
|
||||
self.assertEqual('value1', res_dict['extra_specs']['key1'])
|
||||
|
||||
def test_index_no_data(self):
|
||||
self.stubs.Set(manila.db, 'volume_type_extra_specs_get',
|
||||
return_empty_volume_type_extra_specs)
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
res_dict = self.controller.index(req, 1)
|
||||
|
||||
self.assertEqual(0, len(res_dict['extra_specs']))
|
||||
|
||||
def test_show(self):
|
||||
self.stubs.Set(manila.db, 'volume_type_extra_specs_get',
|
||||
return_volume_type_extra_specs)
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path + '/key5')
|
||||
res_dict = self.controller.show(req, 1, 'key5')
|
||||
|
||||
self.assertEqual('value5', res_dict['key5'])
|
||||
|
||||
def test_show_spec_not_found(self):
|
||||
self.stubs.Set(manila.db, 'volume_type_extra_specs_get',
|
||||
return_empty_volume_type_extra_specs)
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path + '/key6')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
|
||||
req, 1, 'key6')
|
||||
|
||||
def test_delete(self):
|
||||
self.stubs.Set(manila.db, 'volume_type_extra_specs_delete',
|
||||
delete_volume_type_extra_specs)
|
||||
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank(self.api_path + '/key5')
|
||||
self.controller.delete(req, 1, 'key5')
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
|
||||
def test_delete_not_found(self):
|
||||
self.stubs.Set(manila.db, 'volume_type_extra_specs_delete',
|
||||
delete_volume_type_extra_specs_not_found)
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path + '/key6')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
|
||||
req, 1, 'key6')
|
||||
|
||||
def test_create(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
body = {"extra_specs": {"key1": "value1"}}
|
||||
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
res_dict = self.controller.create(req, 1, body)
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
|
||||
self.assertEqual('value1', res_dict['extra_specs']['key1'])
|
||||
|
||||
def test_create_with_too_small_key(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
too_small_key = ""
|
||||
body = {"extra_specs": {too_small_key: "value"}}
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, 1, body)
|
||||
|
||||
def test_create_with_too_big_key(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
too_big_key = "k" * 256
|
||||
body = {"extra_specs": {too_big_key: "value"}}
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, 1, body)
|
||||
|
||||
def test_create_with_too_small_value(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
too_small_value = ""
|
||||
body = {"extra_specs": {"key": too_small_value}}
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, 1, body)
|
||||
|
||||
def test_create_with_too_big_value(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
too_big_value = "v" * 256
|
||||
body = {"extra_specs": {"key": too_big_value}}
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, 1, body)
|
||||
|
||||
@mock.patch.object(manila.db, 'volume_type_extra_specs_update_or_create')
|
||||
def test_create_key_allowed_chars(
|
||||
self, volume_type_extra_specs_update_or_create):
|
||||
mock_return_value = {"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5"}
|
||||
volume_type_extra_specs_update_or_create.\
|
||||
return_value = mock_return_value
|
||||
|
||||
body = {"extra_specs": {"other_alphanum.-_:": "value1"}}
|
||||
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
res_dict = self.controller.create(req, 1, body)
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
self.assertEqual('value1',
|
||||
res_dict['extra_specs']['other_alphanum.-_:'])
|
||||
|
||||
@mock.patch.object(manila.db, 'volume_type_extra_specs_update_or_create')
|
||||
def test_create_too_many_keys_allowed_chars(
|
||||
self, volume_type_extra_specs_update_or_create):
|
||||
mock_return_value = {"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5"}
|
||||
volume_type_extra_specs_update_or_create.\
|
||||
return_value = mock_return_value
|
||||
|
||||
body = {"extra_specs": {"other_alphanum.-_:": "value1",
|
||||
"other2_alphanum.-_:": "value2",
|
||||
"other3_alphanum.-_:": "value3"}}
|
||||
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path)
|
||||
res_dict = self.controller.create(req, 1, body)
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
self.assertEqual('value1',
|
||||
res_dict['extra_specs']['other_alphanum.-_:'])
|
||||
self.assertEqual('value2',
|
||||
res_dict['extra_specs']['other2_alphanum.-_:'])
|
||||
self.assertEqual('value3',
|
||||
res_dict['extra_specs']['other3_alphanum.-_:'])
|
||||
|
||||
def test_update_item(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
body = {"key1": "value1"}
|
||||
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank(self.api_path + '/key1')
|
||||
res_dict = self.controller.update(req, 1, 'key1', body)
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
|
||||
self.assertEqual('value1', res_dict['key1'])
|
||||
|
||||
def test_update_item_too_many_keys(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
body = {"key1": "value1", "key2": "value2"}
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path + '/key1')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
||||
req, 1, 'key1', body)
|
||||
|
||||
def test_update_item_body_uri_mismatch(self):
|
||||
self.stubs.Set(manila.db,
|
||||
'volume_type_extra_specs_update_or_create',
|
||||
return_create_volume_type_extra_specs)
|
||||
body = {"key1": "value1"}
|
||||
|
||||
req = fakes.HTTPRequest.blank(self.api_path + '/bad')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
||||
req, 1, 'bad', body)
|
||||
|
||||
def _extra_specs_empty_update(self, body):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/1/extra_specs')
|
||||
req.method = 'POST'
|
||||
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.update, req, '1', body)
|
||||
|
||||
def test_update_no_body(self):
|
||||
self._extra_specs_empty_update(body=None)
|
||||
|
||||
def test_update_empty_body(self):
|
||||
self._extra_specs_empty_update(body={})
|
||||
|
||||
def _extra_specs_create_bad_body(self, body):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/1/extra_specs')
|
||||
req.method = 'POST'
|
||||
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, '1', body)
|
||||
|
||||
def test_create_no_body(self):
|
||||
self._extra_specs_create_bad_body(body=None)
|
||||
|
||||
def test_create_missing_volume(self):
|
||||
body = {'foo': {'a': 'b'}}
|
||||
self._extra_specs_create_bad_body(body=body)
|
||||
|
||||
def test_create_malformed_entity(self):
|
||||
body = {'extra_specs': 'string'}
|
||||
self._extra_specs_create_bad_body(body=body)
|
||||
|
||||
def test_create_invalid_key(self):
|
||||
body = {"extra_specs": {"ke/y1": "value1"}}
|
||||
self._extra_specs_create_bad_body(body=body)
|
||||
|
||||
def test_create_invalid_too_many_key(self):
|
||||
body = {"key1": "value1", "ke/y2": "value2", "key3": "value3"}
|
||||
self._extra_specs_create_bad_body(body=body)
|
|
@ -0,0 +1,148 @@
|
|||
# Copyright 2011 OpenStack Foundation
|
||||
# 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 webob
|
||||
|
||||
from manila.api.contrib import types_manage
|
||||
from manila import exception
|
||||
from manila.openstack.common.notifier import api as notifier_api
|
||||
from manila.openstack.common.notifier import test_notifier
|
||||
from manila.share import volume_types
|
||||
from manila import test
|
||||
from manila.tests.api import fakes
|
||||
|
||||
|
||||
def stub_volume_type(id):
|
||||
specs = {"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5"}
|
||||
return dict(id=id, name='vol_type_%s' % str(id), extra_specs=specs)
|
||||
|
||||
|
||||
def return_volume_types_get_volume_type(context, id):
|
||||
if id == "777":
|
||||
raise exception.VolumeTypeNotFound(volume_type_id=id)
|
||||
return stub_volume_type(int(id))
|
||||
|
||||
|
||||
def return_volume_types_destroy(context, name):
|
||||
if name == "777":
|
||||
raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
|
||||
pass
|
||||
|
||||
|
||||
def return_volume_types_with_volumes_destroy(context, id):
|
||||
if id == "1":
|
||||
raise exception.VolumeTypeInUse(volume_type_id=id)
|
||||
pass
|
||||
|
||||
|
||||
def return_volume_types_create(context, name, specs):
|
||||
pass
|
||||
|
||||
|
||||
def return_volume_types_get_by_name(context, name):
|
||||
if name == "777":
|
||||
raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
|
||||
return stub_volume_type(int(name.split("_")[2]))
|
||||
|
||||
|
||||
def make_create_body(name):
|
||||
return {
|
||||
"volume_type": {
|
||||
"name": name,
|
||||
"extra_specs": {
|
||||
"key": "value",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class VolumeTypesManageApiTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(VolumeTypesManageApiTest, self).setUp()
|
||||
self.flags(host='fake',
|
||||
notification_driver=[test_notifier.__name__])
|
||||
self.controller = types_manage.VolumeTypesManageController()
|
||||
"""to reset notifier drivers left over from other api/contrib tests"""
|
||||
notifier_api._reset_drivers()
|
||||
test_notifier.NOTIFICATIONS = []
|
||||
self.stubs.Set(volume_types, 'create',
|
||||
return_volume_types_create)
|
||||
self.stubs.Set(volume_types, 'get_volume_type_by_name',
|
||||
return_volume_types_get_by_name)
|
||||
self.stubs.Set(volume_types, 'get_volume_type',
|
||||
return_volume_types_get_volume_type)
|
||||
self.stubs.Set(volume_types, 'destroy',
|
||||
return_volume_types_destroy)
|
||||
|
||||
def test_volume_types_delete(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/1')
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
self.controller._delete(req, 1)
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
|
||||
def test_volume_types_delete_not_found(self):
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/777')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller._delete,
|
||||
req, '777')
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
|
||||
def test_volume_types_with_volumes_destroy(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/1')
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
self.controller._delete(req, 1)
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
|
||||
def test_create(self):
|
||||
body = make_create_body("volume_type_1")
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types')
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
res_dict = self.controller._create(req, body)
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 1)
|
||||
self.assertEqual(1, len(res_dict))
|
||||
self.assertEqual('vol_type_1', res_dict['volume_type']['name'])
|
||||
|
||||
def test_create_with_too_small_name(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types')
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller._create, req, make_create_body(""))
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
|
||||
def test_create_with_too_big_name(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types')
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller._create,
|
||||
req, make_create_body("n" * 256))
|
||||
self.assertEqual(len(test_notifier.NOTIFICATIONS), 0)
|
||||
|
||||
def _create_volume_type_bad_body(self, body):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types')
|
||||
req.method = 'POST'
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller._create, req, body)
|
||||
|
||||
def test_create_no_body(self):
|
||||
self._create_volume_type_bad_body(body=None)
|
||||
|
||||
def test_create_missing_volume(self):
|
||||
self._create_volume_type_bad_body(body={'foo': {'a': 'b'}})
|
||||
|
||||
def test_create_malformed_entity(self):
|
||||
self._create_volume_type_bad_body(body={'volume_type': 'string'})
|
|
@ -149,6 +149,7 @@ class ShareApiTest(test.TestCase):
|
|||
'snapshot_id': '2',
|
||||
'share_network_id': None,
|
||||
'status': 'fakestatus',
|
||||
'volume_type': '1',
|
||||
'links': [{'href': 'http://localhost/v1/fake/shares/1',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://localhost/fake/shares/1',
|
||||
|
@ -254,6 +255,7 @@ class ShareApiTest(test.TestCase):
|
|||
'share_network_id': None,
|
||||
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||
'size': 1,
|
||||
'volume_type': '1',
|
||||
'links': [
|
||||
{
|
||||
'href': 'http://localhost/v1/fake/shares/1',
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
# Copyright 2011 OpenStack Foundation
|
||||
# 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 webob
|
||||
|
||||
from manila.api.v1 import volume_types as types
|
||||
from manila.api.views import types as views_types
|
||||
from manila import exception
|
||||
from manila.openstack.common import timeutils
|
||||
from manila.share import volume_types
|
||||
from manila import test
|
||||
from manila.tests.api import fakes
|
||||
|
||||
|
||||
def stub_volume_type(id):
|
||||
specs = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5"
|
||||
}
|
||||
return dict(
|
||||
id=id,
|
||||
name='vol_type_%s' % str(id),
|
||||
extra_specs=specs,
|
||||
)
|
||||
|
||||
|
||||
def return_volume_types_get_all_types(context):
|
||||
return dict(
|
||||
vol_type_1=stub_volume_type(1),
|
||||
vol_type_2=stub_volume_type(2),
|
||||
vol_type_3=stub_volume_type(3)
|
||||
)
|
||||
|
||||
|
||||
def return_empty_volume_types_get_all_types(context):
|
||||
return {}
|
||||
|
||||
|
||||
def return_volume_types_get_volume_type(context, id):
|
||||
if id == "777":
|
||||
raise exception.VolumeTypeNotFound(volume_type_id=id)
|
||||
return stub_volume_type(int(id))
|
||||
|
||||
|
||||
def return_volume_types_get_by_name(context, name):
|
||||
if name == "777":
|
||||
raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
|
||||
return stub_volume_type(int(name.split("_")[2]))
|
||||
|
||||
|
||||
class VolumeTypesApiTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(VolumeTypesApiTest, self).setUp()
|
||||
self.controller = types.VolumeTypesController()
|
||||
|
||||
def test_volume_types_index(self):
|
||||
self.stubs.Set(volume_types, 'get_all_types',
|
||||
return_volume_types_get_all_types)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types')
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
self.assertEqual(3, len(res_dict['volume_types']))
|
||||
|
||||
expected_names = ['vol_type_1', 'vol_type_2', 'vol_type_3']
|
||||
actual_names = map(lambda e: e['name'], res_dict['volume_types'])
|
||||
self.assertEqual(set(actual_names), set(expected_names))
|
||||
for entry in res_dict['volume_types']:
|
||||
self.assertEqual('value1', entry['extra_specs']['key1'])
|
||||
|
||||
def test_volume_types_index_no_data(self):
|
||||
self.stubs.Set(volume_types, 'get_all_types',
|
||||
return_empty_volume_types_get_all_types)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types')
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
self.assertEqual(0, len(res_dict['volume_types']))
|
||||
|
||||
def test_volume_types_show(self):
|
||||
self.stubs.Set(volume_types, 'get_volume_type',
|
||||
return_volume_types_get_volume_type)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/1')
|
||||
res_dict = self.controller.show(req, 1)
|
||||
|
||||
self.assertEqual(1, len(res_dict))
|
||||
self.assertEqual('1', res_dict['volume_type']['id'])
|
||||
self.assertEqual('vol_type_1', res_dict['volume_type']['name'])
|
||||
|
||||
def test_volume_types_show_not_found(self):
|
||||
self.stubs.Set(volume_types, 'get_volume_type',
|
||||
return_volume_types_get_volume_type)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/777')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
|
||||
req, '777')
|
||||
|
||||
def test_view_builder_show(self):
|
||||
view_builder = views_types.ViewBuilder()
|
||||
|
||||
now = timeutils.isotime()
|
||||
raw_volume_type = dict(
|
||||
name='new_type',
|
||||
deleted=False,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
extra_specs={},
|
||||
deleted_at=None,
|
||||
id=42,
|
||||
)
|
||||
|
||||
request = fakes.HTTPRequest.blank("/v2")
|
||||
output = view_builder.show(request, raw_volume_type)
|
||||
|
||||
self.assertIn('volume_type', output)
|
||||
expected_volume_type = dict(
|
||||
name='new_type',
|
||||
extra_specs={},
|
||||
id=42,
|
||||
)
|
||||
self.assertDictMatch(output['volume_type'], expected_volume_type)
|
||||
|
||||
def test_view_builder_list(self):
|
||||
view_builder = views_types.ViewBuilder()
|
||||
|
||||
now = timeutils.isotime()
|
||||
raw_volume_types = []
|
||||
for i in range(0, 10):
|
||||
raw_volume_types.append(
|
||||
dict(
|
||||
name='new_type',
|
||||
deleted=False,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
extra_specs={},
|
||||
deleted_at=None,
|
||||
id=42 + i
|
||||
)
|
||||
)
|
||||
|
||||
request = fakes.HTTPRequest.blank("/v2")
|
||||
output = view_builder.index(request, raw_volume_types)
|
||||
|
||||
self.assertIn('volume_types', output)
|
||||
for i in range(0, 10):
|
||||
expected_volume_type = dict(
|
||||
name='new_type',
|
||||
extra_specs={},
|
||||
id=42 + i
|
||||
)
|
||||
self.assertDictMatch(output['volume_types'][i],
|
||||
expected_volume_type)
|
|
@ -18,6 +18,8 @@
|
|||
"share:update_share_metadata": [],
|
||||
"share_extension:share_admin_actions:reset_status": [["rule:admin_api"]],
|
||||
"share_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]],
|
||||
"share_extension:types_manage": [],
|
||||
"share_extension:types_extra_specs": [],
|
||||
|
||||
"limits_extension:used_limits": []
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ def fake_share(id, **kwargs):
|
|||
'project_id': 'fakeproject',
|
||||
'snapshot_id': None,
|
||||
'share_network_id': None,
|
||||
'volume_type_id': None,
|
||||
'availability_zone': 'fakeaz',
|
||||
'status': 'fakestatus',
|
||||
'display_name': 'fakename',
|
||||
|
@ -136,6 +137,7 @@ class ShareAPITestCase(test.TestCase):
|
|||
'share_proto': share['share_proto'],
|
||||
'share_id': share['id'],
|
||||
'snapshot_id': share['snapshot_id'],
|
||||
'volume_type': None
|
||||
}
|
||||
|
||||
self.mox.StubOutWithMock(db_driver, 'share_create')
|
||||
|
@ -243,6 +245,7 @@ class ShareAPITestCase(test.TestCase):
|
|||
request_spec = {'share_properties': options,
|
||||
'share_proto': share['share_proto'],
|
||||
'share_id': share['id'],
|
||||
'volume_type': None,
|
||||
'snapshot_id': share['snapshot_id'],
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue