Nova Flavor Resource Update
Add ability to associate private flavors with multiple tenants. Also updated 'test_flavor' file to reflect changes and test functionality. Change-Id: I841460b936ae52b3a91981029b42212ae484c3e4 Implements: blueprint nova-flavor-resource
This commit is contained in:
parent
2ea7781920
commit
1b64098cdf
|
@ -10,13 +10,19 @@
|
|||
# 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 heat.common import exception
|
||||
from oslo_log import log as logging
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.common.i18n import _LI
|
||||
from heat.engine import attributes
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine import support
|
||||
from heat.engine import translation
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NovaFlavor(resource.Resource):
|
||||
|
@ -41,10 +47,10 @@ class NovaFlavor(resource.Resource):
|
|||
entity = 'flavors'
|
||||
|
||||
PROPERTIES = (
|
||||
ID, NAME, RAM, VCPUS, DISK, SWAP,
|
||||
TENANTS, ID, NAME, RAM, VCPUS, DISK, SWAP,
|
||||
EPHEMERAL, RXTX_FACTOR, EXTRA_SPECS, IS_PUBLIC
|
||||
) = (
|
||||
'flavorid', 'name', 'ram', 'vcpus', 'disk', 'swap',
|
||||
'tenants', 'flavorid', 'name', 'ram', 'vcpus', 'disk', 'swap',
|
||||
'ephemeral', 'rxtx_factor', 'extra_specs', 'is_public',
|
||||
)
|
||||
|
||||
|
@ -55,6 +61,17 @@ class NovaFlavor(resource.Resource):
|
|||
)
|
||||
|
||||
properties_schema = {
|
||||
TENANTS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('List of tenants.'),
|
||||
update_allowed=True,
|
||||
default=[],
|
||||
schema=properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
constraints=[constraints.CustomConstraint('keystone.project')]
|
||||
),
|
||||
support_status=support.SupportStatus(version='8.0.0')
|
||||
),
|
||||
ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Unique ID of the flavor. If not specified, '
|
||||
|
@ -127,6 +144,17 @@ class NovaFlavor(resource.Resource):
|
|||
)
|
||||
}
|
||||
|
||||
def translation_rules(self, properties):
|
||||
return [
|
||||
translation.TranslationRule(
|
||||
properties,
|
||||
translation.TranslationRule.RESOLVE,
|
||||
[self.TENANTS],
|
||||
client_plugin=self.client_plugin(),
|
||||
finder='get_project_id'
|
||||
)
|
||||
]
|
||||
|
||||
def handle_create(self):
|
||||
args = dict(self.properties)
|
||||
if not args['flavorid']:
|
||||
|
@ -134,15 +162,23 @@ class NovaFlavor(resource.Resource):
|
|||
if not args['name']:
|
||||
args['name'] = self.physical_resource_name()
|
||||
flavor_keys = args.pop(self.EXTRA_SPECS)
|
||||
tenants = args.pop(self.TENANTS)
|
||||
flavor = self.client().flavors.create(**args)
|
||||
self.resource_id_set(flavor.id)
|
||||
if flavor_keys:
|
||||
flavor.set_keys(flavor_keys)
|
||||
|
||||
if not self.IS_PUBLIC:
|
||||
if not tenants:
|
||||
LOG.info(_LI('Tenant property is recommended if IS_PUBLIC'
|
||||
'is false.'))
|
||||
tenant = self.stack.context.tenant_id
|
||||
if not args['is_public']:
|
||||
# grant access only to the active project(private flavor)
|
||||
self.client().flavor_access.add_tenant_access(flavor, tenant)
|
||||
else:
|
||||
for tenant in tenants:
|
||||
# grant access only to the active project(private flavor)
|
||||
self.client().flavor_access.add_tenant_access(flavor,
|
||||
tenant)
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
"""Update nova flavor."""
|
||||
|
@ -153,6 +189,23 @@ class NovaFlavor(resource.Resource):
|
|||
new_keys = prop_diff.get(self.EXTRA_SPECS)
|
||||
if new_keys is not None:
|
||||
flavor.set_keys(new_keys)
|
||||
"""Update tenant access list."""
|
||||
if self.TENANTS in prop_diff and not self.IS_PUBLIC:
|
||||
kwargs = {'flavor': self.resource_id}
|
||||
old_tenants = [
|
||||
x.tenant_id for x in self.client().flavor_access.list(**kwargs)
|
||||
] or []
|
||||
new_tenants = prop_diff.get(self.TENANTS) or []
|
||||
tenants_to_remove = list(set(old_tenants) - set(new_tenants))
|
||||
tenants_to_add = list(set(new_tenants) - set(old_tenants))
|
||||
if tenants_to_remove or tenants_to_add:
|
||||
flavor = self.client().flavors.get(self.resource_id)
|
||||
for _tenant in tenants_to_remove:
|
||||
self.client().flavor_access.remove_tenant_access(flavor,
|
||||
_tenant)
|
||||
for _tenant in tenants_to_add:
|
||||
self.client().flavor_access.add_tenant_access(flavor,
|
||||
_tenant)
|
||||
|
||||
def _resolve_attribute(self, name):
|
||||
if self.resource_id is None:
|
||||
|
|
|
@ -31,7 +31,8 @@ flavor_template = {
|
|||
'swap': 2,
|
||||
'rxtx_factor': 1.0,
|
||||
'ephemeral': 0,
|
||||
'extra_specs': {"foo": "bar"}
|
||||
'extra_specs': {"foo": "bar"},
|
||||
'tenants': []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,11 +46,15 @@ class NovaFlavorTest(common.HeatTestCase):
|
|||
return_value=True)
|
||||
self.ctx = utils.dummy_context()
|
||||
|
||||
def create_flavor(self, with_name_id=False):
|
||||
def create_flavor(self, with_name_id=False, is_public=True):
|
||||
if with_name_id:
|
||||
props = flavor_template['resources']['my_flavor']['properties']
|
||||
props['name'] = 'test_flavor'
|
||||
props['flavorid'] = '1234'
|
||||
if not is_public:
|
||||
props = flavor_template['resources']['my_flavor']['properties']
|
||||
props['is_public'] = False
|
||||
props['tenants'] = ["foo", "bar"]
|
||||
self.stack = stack.Stack(
|
||||
self.ctx, 'nova_flavor_test_stack',
|
||||
template.Template(flavor_template)
|
||||
|
@ -108,17 +113,21 @@ class NovaFlavorTest(common.HeatTestCase):
|
|||
self.assertTrue(self.my_flavor.FnGetAtt('is_public'))
|
||||
|
||||
def test_private_flavor_handle_create(self):
|
||||
self.create_flavor()
|
||||
self.create_flavor(is_public=False)
|
||||
value = mock.MagicMock()
|
||||
flavor_id = '927202df-1afb-497f-8368-9c2d2f26e5db'
|
||||
value.id = flavor_id
|
||||
value.is_public = False
|
||||
self.my_flavor.IS_PUBLIC = False
|
||||
self.flavors.create.return_value = value
|
||||
self.flavors.get.return_value = value
|
||||
self.my_flavor.handle_create()
|
||||
value.set_keys.assert_called_once_with({"foo": "bar"})
|
||||
self.assertEqual(flavor_id, self.my_flavor.resource_id)
|
||||
self.assertFalse(self.my_flavor.FnGetAtt('is_public'))
|
||||
client_test = self.my_flavor.client().flavor_access.add_tenant_access
|
||||
test_tenants = [mock.call(value, 'foo'), mock.call(value, 'bar')]
|
||||
self.assertEqual(test_tenants, client_test.call_args_list)
|
||||
|
||||
def test_flavor_handle_update_keys(self):
|
||||
self.create_flavor()
|
||||
|
@ -133,6 +142,47 @@ class NovaFlavorTest(common.HeatTestCase):
|
|||
value.unset_keys.assert_called_once_with({})
|
||||
value.set_keys.assert_called_once_with(new_keys)
|
||||
|
||||
def test_flavor_handle_update_add_tenants(self):
|
||||
self.create_flavor()
|
||||
value = mock.MagicMock()
|
||||
|
||||
new_tenants = ["new_foo", "new_bar"]
|
||||
prop_diff = {'tenants': new_tenants}
|
||||
self.my_flavor.IS_PUBLIC = False
|
||||
self.flavors.get.return_value = value
|
||||
|
||||
self.my_flavor.handle_update(json_snippet=None,
|
||||
tmpl_diff=None, prop_diff=prop_diff)
|
||||
test_tenants_add = [mock.call(value, 'new_foo'),
|
||||
mock.call(value, 'new_bar')]
|
||||
test_add = self.my_flavor.client().flavor_access.add_tenant_access
|
||||
self.assertItemsEqual(test_tenants_add,
|
||||
test_add.call_args_list)
|
||||
|
||||
def test_flavor_handle_update_remove_tenants(self):
|
||||
self.create_flavor(is_public=False)
|
||||
value = mock.MagicMock()
|
||||
|
||||
new_tenants = []
|
||||
prop_diff = {'tenants': new_tenants}
|
||||
self.my_flavor.IS_PUBLIC = False
|
||||
self.flavors.get.return_value = value
|
||||
|
||||
itemFoo = mock.MagicMock()
|
||||
itemFoo.tenant_id = 'foo'
|
||||
itemBar = mock.MagicMock()
|
||||
itemBar.tenant_id = 'bar'
|
||||
|
||||
self.my_flavor.client().flavor_access.list.return_value = [itemFoo,
|
||||
itemBar]
|
||||
self.my_flavor.handle_update(json_snippet=None,
|
||||
tmpl_diff=None, prop_diff=prop_diff)
|
||||
test_tenants_remove = [mock.call(value, 'foo'),
|
||||
mock.call(value, 'bar')]
|
||||
test_rem = self.my_flavor.client().flavor_access.remove_tenant_access
|
||||
self.assertItemsEqual(test_tenants_remove,
|
||||
test_rem.call_args_list)
|
||||
|
||||
def test_flavor_show_resource(self):
|
||||
self.create_flavor()
|
||||
self.my_flavor.resource_id = 'flavor_test_id'
|
||||
|
|
Loading…
Reference in New Issue