8415297e5b
According to spec description [1] binary data should always be immutable. Before this commit, passing 'mutable' argument lead to exception, because it was passed to a function twice. This commit makes BinaryObject's silently ignore mutable=False and raises an appropriate exception in case mutable=True is passed. Change-Id: Ib42871004c792b840f8af0316c3b8177c4ecf706 Closes-Bug: #1542430
1129 lines
43 KiB
Python
1129 lines
43 KiB
Python
# Copyright (c) 2015 Mirantis, 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.
|
|
|
|
import datetime
|
|
import functools
|
|
|
|
import mock
|
|
import six
|
|
|
|
import glance.common.exception as exc
|
|
from glance.common.glare import declarative
|
|
import glance.common.glare.definitions as defs
|
|
from glance.common.glare import serialization
|
|
import glance.tests.utils as test_utils
|
|
|
|
|
|
BASE = declarative.get_declarative_base()
|
|
|
|
|
|
class TestDeclarativeProperties(test_utils.BaseTestCase):
|
|
def test_artifact_type_properties(self):
|
|
class SomeTypeWithNoExplicitName(BASE):
|
|
some_attr = declarative.AttributeDefinition()
|
|
|
|
class InheritedType(SomeTypeWithNoExplicitName):
|
|
__type_version__ = '1.0'
|
|
__type_name__ = 'ExplicitName'
|
|
__type_description__ = 'Type description'
|
|
__type_display_name__ = 'EXPLICIT_NAME'
|
|
__endpoint__ = 'some_endpoint'
|
|
|
|
some_attr = declarative.AttributeDefinition(display_name='NAME')
|
|
|
|
base_type = SomeTypeWithNoExplicitName
|
|
base_instance = SomeTypeWithNoExplicitName()
|
|
self.assertIsNotNone(base_type.metadata)
|
|
self.assertIsNotNone(base_instance.metadata)
|
|
self.assertEqual(base_type.metadata, base_instance.metadata)
|
|
self.assertEqual("SomeTypeWithNoExplicitName",
|
|
base_type.metadata.type_name)
|
|
self.assertEqual("SomeTypeWithNoExplicitName",
|
|
base_type.metadata.type_display_name)
|
|
self.assertEqual("1.0", base_type.metadata.type_version)
|
|
self.assertIsNone(base_type.metadata.type_description)
|
|
self.assertEqual('sometypewithnoexplicitname',
|
|
base_type.metadata.endpoint)
|
|
|
|
self.assertIsNone(base_instance.some_attr)
|
|
self.assertIsNotNone(base_type.some_attr)
|
|
self.assertEqual(base_type.some_attr,
|
|
base_instance.metadata.attributes.all['some_attr'])
|
|
self.assertEqual('some_attr', base_type.some_attr.name)
|
|
self.assertEqual('some_attr', base_type.some_attr.display_name)
|
|
self.assertIsNone(base_type.some_attr.description)
|
|
|
|
derived_type = InheritedType
|
|
derived_instance = InheritedType()
|
|
|
|
self.assertIsNotNone(derived_type.metadata)
|
|
self.assertIsNotNone(derived_instance.metadata)
|
|
self.assertEqual(derived_type.metadata, derived_instance.metadata)
|
|
self.assertEqual('ExplicitName', derived_type.metadata.type_name)
|
|
self.assertEqual('EXPLICIT_NAME',
|
|
derived_type.metadata.type_display_name)
|
|
self.assertEqual('1.0', derived_type.metadata.type_version)
|
|
self.assertEqual('Type description',
|
|
derived_type.metadata.type_description)
|
|
self.assertEqual('some_endpoint', derived_type.metadata.endpoint)
|
|
self.assertIsNone(derived_instance.some_attr)
|
|
self.assertIsNotNone(derived_type.some_attr)
|
|
self.assertEqual(derived_type.some_attr,
|
|
derived_instance.metadata.attributes.all['some_attr'])
|
|
self.assertEqual('some_attr', derived_type.some_attr.name)
|
|
self.assertEqual('NAME', derived_type.some_attr.display_name)
|
|
|
|
def test_wrong_type_definition(self):
|
|
def declare_wrong_type_version():
|
|
class WrongType(BASE):
|
|
__type_version__ = 'abc' # not a semver
|
|
|
|
return WrongType
|
|
|
|
def declare_wrong_type_name():
|
|
class WrongType(BASE):
|
|
__type_name__ = 'a' * 256 # too long
|
|
|
|
return WrongType
|
|
|
|
self.assertRaises(exc.InvalidArtifactTypeDefinition,
|
|
declare_wrong_type_version)
|
|
self.assertRaises(exc.InvalidArtifactTypeDefinition,
|
|
declare_wrong_type_name)
|
|
|
|
def test_base_declarative_attributes(self):
|
|
class TestType(BASE):
|
|
defaulted = declarative.PropertyDefinition(default=42)
|
|
read_only = declarative.PropertyDefinition(readonly=True)
|
|
required_attr = declarative.PropertyDefinition(required=True)
|
|
|
|
e = self.assertRaises(exc.InvalidArtifactPropertyValue, TestType)
|
|
self.assertEqual('required_attr', e.name)
|
|
self.assertIsNone(e.value)
|
|
tt = TestType(required_attr="universe")
|
|
self.assertEqual('universe', tt.required_attr)
|
|
self.assertEqual(42, tt.defaulted)
|
|
self.assertIsNone(tt.read_only)
|
|
|
|
tt = TestType(required_attr="universe", defaulted=0, read_only="Hello")
|
|
self.assertEqual(0, tt.defaulted)
|
|
self.assertEqual("Hello", tt.read_only)
|
|
|
|
tt.defaulted = 5
|
|
self.assertEqual(5, tt.defaulted)
|
|
tt.required_attr = 'Foo'
|
|
self.assertEqual('Foo', tt.required_attr)
|
|
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'read_only', 'some_val')
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'required_attr', None)
|
|
|
|
# no type checks in base AttributeDefinition
|
|
o = object()
|
|
tt.required_attr = o
|
|
self.assertEqual(o, tt.required_attr)
|
|
|
|
def test_generic_property(self):
|
|
class TestType(BASE):
|
|
simple_prop = declarative.PropertyDefinition()
|
|
immutable_internal = declarative.PropertyDefinition(mutable=False,
|
|
internal=True)
|
|
prop_with_allowed = declarative.PropertyDefinition(
|
|
allowed_values=["Foo", True, 42])
|
|
|
|
class DerivedType(TestType):
|
|
prop_with_allowed = declarative.PropertyDefinition(
|
|
allowed_values=["Foo", True, 42], required=True, default=42)
|
|
|
|
tt = TestType()
|
|
self.assertEqual(True,
|
|
tt.metadata.attributes.all['simple_prop'].mutable)
|
|
self.assertEqual(False,
|
|
tt.metadata.attributes.all['simple_prop'].internal)
|
|
self.assertEqual(False,
|
|
tt.metadata.attributes.all[
|
|
'immutable_internal'].mutable)
|
|
self.assertEqual(True,
|
|
tt.metadata.attributes.all[
|
|
'immutable_internal'].internal)
|
|
self.assertIsNone(tt.prop_with_allowed)
|
|
tt = TestType(prop_with_allowed=42)
|
|
self.assertEqual(42, tt.prop_with_allowed)
|
|
tt = TestType(prop_with_allowed=True)
|
|
self.assertEqual(True, tt.prop_with_allowed)
|
|
tt = TestType(prop_with_allowed='Foo')
|
|
self.assertEqual('Foo', tt.prop_with_allowed)
|
|
|
|
tt.prop_with_allowed = 42
|
|
self.assertEqual(42, tt.prop_with_allowed)
|
|
tt.prop_with_allowed = 'Foo'
|
|
self.assertEqual('Foo', tt.prop_with_allowed)
|
|
tt.prop_with_allowed = True
|
|
self.assertEqual(True, tt.prop_with_allowed)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr,
|
|
tt, 'prop_with_allowed', 'bar')
|
|
# ensure that wrong assignment didn't change the value
|
|
self.assertEqual(True, tt.prop_with_allowed)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, TestType,
|
|
prop_with_allowed=False)
|
|
|
|
dt = DerivedType()
|
|
self.assertEqual(42, dt.prop_with_allowed)
|
|
|
|
def test_default_violates_allowed(self):
|
|
def declare_wrong_type():
|
|
class WrongType(BASE):
|
|
prop = declarative.PropertyDefinition(
|
|
allowed_values=['foo', 'bar'],
|
|
default='baz')
|
|
|
|
return WrongType
|
|
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
declare_wrong_type)
|
|
|
|
def test_string_property(self):
|
|
class TestType(BASE):
|
|
simple = defs.String()
|
|
with_length = defs.String(max_length=10, min_length=5)
|
|
with_pattern = defs.String(pattern='^\\d+$', default='42')
|
|
|
|
tt = TestType()
|
|
tt.simple = 'foo'
|
|
self.assertEqual('foo', tt.simple)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr,
|
|
tt, 'simple', 42)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr,
|
|
tt, 'simple', 'x' * 256)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, TestType,
|
|
simple='x' * 256)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr,
|
|
tt, 'with_length', 'x' * 11)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr,
|
|
tt, 'with_length', 'x' * 4)
|
|
tt.simple = 'x' * 5
|
|
self.assertEqual('x' * 5, tt.simple)
|
|
tt.simple = 'x' * 10
|
|
self.assertEqual('x' * 10, tt.simple)
|
|
|
|
self.assertEqual("42", tt.with_pattern)
|
|
tt.with_pattern = '0'
|
|
self.assertEqual('0', tt.with_pattern)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'with_pattern', 'abc')
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'with_pattern', '.123.')
|
|
|
|
def test_binary_object_mutable(self):
|
|
def declare_blob(mutable):
|
|
class BLOB(BASE):
|
|
prop = defs.BinaryObject(mutable=mutable)
|
|
|
|
return BLOB
|
|
|
|
blob = declare_blob(False)()
|
|
self.assertFalse(blob.metadata.attributes.all['prop'].mutable)
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
functools.partial(declare_blob, True))
|
|
|
|
def test_default_and_allowed_violates_string_constrains(self):
|
|
def declare_wrong_default():
|
|
class WrongType(BASE):
|
|
prop = defs.String(min_length=4, default='foo')
|
|
|
|
return WrongType
|
|
|
|
def declare_wrong_allowed():
|
|
class WrongType(BASE):
|
|
prop = defs.String(min_length=4, allowed_values=['foo', 'bar'])
|
|
|
|
return WrongType
|
|
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
declare_wrong_default)
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
declare_wrong_allowed)
|
|
|
|
def test_integer_property(self):
|
|
class TestType(BASE):
|
|
simple = defs.Integer()
|
|
constrained = defs.Integer(min_value=10, max_value=50)
|
|
|
|
tt = TestType()
|
|
self.assertIsNone(tt.simple)
|
|
self.assertIsNone(tt.constrained)
|
|
|
|
tt.simple = 0
|
|
tt.constrained = 10
|
|
self.assertEqual(0, tt.simple)
|
|
self.assertEqual(10, tt.constrained)
|
|
|
|
tt.constrained = 50
|
|
self.assertEqual(50, tt.constrained)
|
|
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'constrained', 1)
|
|
self.assertEqual(50, tt.constrained)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'constrained', 51)
|
|
self.assertEqual(50, tt.constrained)
|
|
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'simple', '11')
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'simple', 10.5)
|
|
|
|
def test_default_and_allowed_violates_int_constrains(self):
|
|
def declare_wrong_default():
|
|
class WrongType(BASE):
|
|
prop = defs.Integer(min_value=4, default=1)
|
|
|
|
return WrongType
|
|
|
|
def declare_wrong_allowed():
|
|
class WrongType(BASE):
|
|
prop = defs.Integer(min_value=4, max_value=10,
|
|
allowed_values=[1, 15])
|
|
|
|
return WrongType
|
|
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
declare_wrong_default)
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
declare_wrong_allowed)
|
|
|
|
def test_numeric_values(self):
|
|
class TestType(BASE):
|
|
simple = defs.Numeric()
|
|
constrained = defs.Numeric(min_value=3.14, max_value=4.1)
|
|
|
|
tt = TestType(simple=0.1, constrained=4)
|
|
self.assertEqual(0.1, tt.simple)
|
|
self.assertEqual(4.0, tt.constrained)
|
|
|
|
tt.simple = 1
|
|
self.assertEqual(1, tt.simple)
|
|
tt.constrained = 3.14
|
|
self.assertEqual(3.14, tt.constrained)
|
|
tt.constrained = 4.1
|
|
self.assertEqual(4.1, tt.constrained)
|
|
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'simple', 'qwerty')
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'constrained', 3)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
'constrained', 5)
|
|
|
|
def test_default_and_allowed_violates_numeric_constrains(self):
|
|
def declare_wrong_default():
|
|
class WrongType(BASE):
|
|
prop = defs.Numeric(min_value=4.0, default=1.1)
|
|
|
|
return WrongType
|
|
|
|
def declare_wrong_allowed():
|
|
class WrongType(BASE):
|
|
prop = defs.Numeric(min_value=4.0, max_value=10.0,
|
|
allowed_values=[1.0, 15.5])
|
|
|
|
return WrongType
|
|
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
declare_wrong_default)
|
|
self.assertRaises(exc.InvalidArtifactTypePropertyDefinition,
|
|
declare_wrong_allowed)
|
|
|
|
def test_same_item_type_array(self):
|
|
class TestType(BASE):
|
|
simple = defs.Array()
|
|
unique = defs.Array(unique=True)
|
|
simple_with_allowed_values = defs.Array(
|
|
defs.String(allowed_values=["Foo", "Bar"]))
|
|
defaulted = defs.Array(defs.Boolean(), default=[True, False])
|
|
constrained = defs.Array(item_type=defs.Numeric(min_value=0),
|
|
min_size=3, max_size=5, unique=True)
|
|
|
|
tt = TestType(simple=[])
|
|
self.assertEqual([], tt.simple)
|
|
tt.simple.append("Foo")
|
|
self.assertEqual(["Foo"], tt.simple)
|
|
tt.simple.append("Foo")
|
|
self.assertEqual(["Foo", "Foo"], tt.simple)
|
|
self.assertEqual(2, len(tt.simple))
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.simple.append,
|
|
42)
|
|
tt.simple.pop(1)
|
|
self.assertEqual(["Foo"], tt.simple)
|
|
del tt.simple[0]
|
|
self.assertEqual(0, len(tt.simple))
|
|
|
|
tt.simple_with_allowed_values = ["Foo"]
|
|
tt.simple_with_allowed_values.insert(0, "Bar")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.simple_with_allowed_values.append, "Baz")
|
|
|
|
self.assertEqual([True, False], tt.defaulted)
|
|
tt.defaulted.pop()
|
|
self.assertEqual([True], tt.defaulted)
|
|
tt2 = TestType()
|
|
self.assertEqual([True, False], tt2.defaulted)
|
|
|
|
self.assertIsNone(tt.constrained)
|
|
tt.constrained = [10, 5, 4]
|
|
self.assertEqual([10, 5, 4], tt.constrained)
|
|
tt.constrained[1] = 15
|
|
self.assertEqual([10, 15, 4], tt.constrained)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.constrained.__setitem__, 1, -5)
|
|
self.assertEqual([10, 15, 4], tt.constrained)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.constrained.remove, 15)
|
|
self.assertEqual([10, 15, 4], tt.constrained)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.constrained.__delitem__, 1)
|
|
self.assertEqual([10, 15, 4], tt.constrained)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.constrained.append, 15)
|
|
self.assertEqual([10, 15, 4], tt.constrained)
|
|
|
|
tt.unique = []
|
|
tt.unique.append("foo")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.unique.append,
|
|
"foo")
|
|
|
|
def test_tuple_style_array(self):
|
|
class TestType(BASE):
|
|
address = defs.Array(
|
|
item_type=[defs.String(20), defs.Integer(min_value=1),
|
|
defs.Boolean()])
|
|
|
|
tt = TestType(address=["Hope Street", 1234, True])
|
|
self.assertEqual("Hope Street", tt.address[0])
|
|
self.assertEqual(1234, tt.address[1])
|
|
self.assertEqual(True, tt.address[2])
|
|
|
|
# On Python 3, sort() fails because int (1) and string ("20") are not
|
|
# comparable
|
|
if six.PY2:
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.address.sort)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.address.pop, 0)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.address.pop, 1)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.address.pop)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.address.append,
|
|
"Foo")
|
|
|
|
def test_same_item_type_dict(self):
|
|
class TestType(BASE):
|
|
simple_props = defs.Dict()
|
|
constrained_props = defs.Dict(
|
|
properties=defs.Integer(min_value=1, allowed_values=[1, 2]),
|
|
min_properties=2,
|
|
max_properties=3)
|
|
|
|
tt = TestType()
|
|
self.assertIsNone(tt.simple_props)
|
|
self.assertIsNone(tt.constrained_props)
|
|
tt.simple_props = {}
|
|
self.assertEqual({}, tt.simple_props)
|
|
tt.simple_props["foo"] = "bar"
|
|
self.assertEqual({"foo": "bar"}, tt.simple_props)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.simple_props.__setitem__, 42, "foo")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.simple_props.setdefault, "bar", 42)
|
|
|
|
tt.constrained_props = {"foo": 1, "bar": 2}
|
|
self.assertEqual({"foo": 1, "bar": 2}, tt.constrained_props)
|
|
tt.constrained_props["baz"] = 1
|
|
self.assertEqual({"foo": 1, "bar": 2, "baz": 1}, tt.constrained_props)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.constrained_props.__setitem__, "foo", 3)
|
|
self.assertEqual(1, tt.constrained_props["foo"])
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.constrained_props.__setitem__, "qux", 2)
|
|
tt.constrained_props.pop("foo")
|
|
self.assertEqual({"bar": 2, "baz": 1}, tt.constrained_props)
|
|
tt.constrained_props['qux'] = 2
|
|
self.assertEqual({"qux": 2, "bar": 2, "baz": 1}, tt.constrained_props)
|
|
tt.constrained_props.popitem()
|
|
dict_copy = tt.constrained_props.copy()
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.constrained_props.popitem)
|
|
self.assertEqual(dict_copy, tt.constrained_props)
|
|
|
|
def test_composite_dict(self):
|
|
class TestType(BASE):
|
|
props = defs.Dict(properties={"foo": defs.String(),
|
|
"bar": defs.Boolean()})
|
|
fixed = defs.Dict(properties={"name": defs.String(min_length=2),
|
|
"age": defs.Integer(min_value=0,
|
|
max_value=99)})
|
|
|
|
tt = TestType()
|
|
tt.props = {"foo": "FOO", "bar": False}
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.props.__setitem__, "bar", 123)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.props.__setitem__, "extra", "value")
|
|
tt.fixed = {"name": "Alex", "age": 42}
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.fixed.__setitem__, "age", 120)
|
|
|
|
def test_immutables(self):
|
|
class TestType(BASE):
|
|
activated = defs.Boolean(required=True, default=False)
|
|
name = defs.String(mutable=False)
|
|
|
|
def __is_mutable__(self):
|
|
return not self.activated
|
|
|
|
tt = TestType()
|
|
self.assertEqual(False, tt.activated)
|
|
self.assertIsNone(tt.name)
|
|
tt.name = "Foo"
|
|
self.assertEqual("Foo", tt.name)
|
|
tt.activated = True
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr,
|
|
tt, "name", "Bar")
|
|
self.assertEqual("Foo", tt.name)
|
|
tt.activated = False
|
|
tt.name = "Bar"
|
|
self.assertEqual("Bar", tt.name)
|
|
|
|
def test_readonly_array_dict(self):
|
|
class TestType(BASE):
|
|
arr = defs.Array(readonly=True)
|
|
dict = defs.Dict(readonly=True)
|
|
|
|
tt = TestType(arr=["Foo", "Bar"], dict={"qux": "baz"})
|
|
self.assertEqual(["Foo", "Bar"], tt.arr)
|
|
self.assertEqual({"qux": "baz"}, tt.dict)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.append,
|
|
"Baz")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.insert,
|
|
0, "Baz")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.__setitem__,
|
|
0, "Baz")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.remove,
|
|
"Foo")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.pop)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.dict.pop,
|
|
"qux")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.dict.__setitem__, "qux", "foo")
|
|
|
|
def test_mutable_array_dict(self):
|
|
class TestType(BASE):
|
|
arr = defs.Array(mutable=False)
|
|
dict = defs.Dict(mutable=False)
|
|
activated = defs.Boolean()
|
|
|
|
def __is_mutable__(self):
|
|
return not self.activated
|
|
|
|
tt = TestType()
|
|
tt.arr = []
|
|
tt.dict = {}
|
|
tt.arr.append("Foo")
|
|
tt.arr.insert(0, "Bar")
|
|
tt.dict["baz"] = "qux"
|
|
tt.activated = True
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.append,
|
|
"Baz")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.insert,
|
|
0, "Baz")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.__setitem__,
|
|
0, "Baz")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.remove,
|
|
"Foo")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.pop)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.dict.pop,
|
|
"qux")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.dict.__setitem__, "qux", "foo")
|
|
|
|
def test_readonly_as_write_once(self):
|
|
class TestType(BASE):
|
|
prop = defs.String(readonly=True)
|
|
arr = defs.Array(readonly=True)
|
|
|
|
tt = TestType()
|
|
self.assertIsNone(tt.prop)
|
|
tt.prop = "Foo"
|
|
self.assertEqual("Foo", tt.prop)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt,
|
|
"prop", "bar")
|
|
tt2 = TestType()
|
|
self.assertIsNone(tt2.prop)
|
|
tt2.prop = None
|
|
self.assertIsNone(tt2.prop)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt2,
|
|
"prop", None)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, tt2,
|
|
"prop", "foo")
|
|
self.assertIsNone(tt.arr)
|
|
tt.arr = ["foo", "bar"]
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.append,
|
|
'baz')
|
|
self.assertIsNone(tt2.arr)
|
|
tt2.arr = None
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.arr.append,
|
|
'baz')
|
|
|
|
|
|
class TestArtifactType(test_utils.BaseTestCase):
|
|
def test_create_artifact(self):
|
|
a = defs.ArtifactType(**get_artifact_fixture())
|
|
self.assertIsNotNone(a)
|
|
self.assertEqual("123", a.id)
|
|
self.assertEqual("ArtifactType", a.type_name)
|
|
self.assertEqual("1.0", a.type_version)
|
|
self.assertEqual("11.2", a.version)
|
|
self.assertEqual("Foo", a.name)
|
|
self.assertEqual("private", a.visibility)
|
|
self.assertEqual("creating", a.state)
|
|
self.assertEqual("my_tenant", a.owner)
|
|
self.assertEqual(a.created_at, a.updated_at)
|
|
self.assertIsNone(a.description)
|
|
self.assertIsNone(a.published_at)
|
|
self.assertIsNone(a.deleted_at)
|
|
|
|
self.assertIsNone(a.description)
|
|
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a, "id",
|
|
"foo")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a,
|
|
"state", "active")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a,
|
|
"owner", "some other")
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a,
|
|
"created_at", datetime.datetime.now())
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a,
|
|
"deleted_at", datetime.datetime.now())
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a,
|
|
"updated_at", datetime.datetime.now())
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a,
|
|
"published_at", datetime.datetime.now())
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, a,
|
|
"visibility", "wrong")
|
|
|
|
def test_dependency_prop(self):
|
|
class DerivedType(defs.ArtifactType):
|
|
depends_on_any = defs.ArtifactReference()
|
|
depends_on_self = defs.ArtifactReference(type_name='DerivedType')
|
|
depends_on_self_version = defs.ArtifactReference(
|
|
type_name='DerivedType',
|
|
type_version='1.0')
|
|
|
|
class DerivedTypeV11(DerivedType):
|
|
__type_name__ = 'DerivedType'
|
|
__type_version__ = '1.1'
|
|
depends_on_self_version = defs.ArtifactReference(
|
|
type_name='DerivedType',
|
|
type_version='1.1')
|
|
|
|
d1 = DerivedType(**get_artifact_fixture())
|
|
d2 = DerivedTypeV11(**get_artifact_fixture())
|
|
a = defs.ArtifactType(**get_artifact_fixture())
|
|
d1.depends_on_any = a
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, d1,
|
|
'depends_on_self', a)
|
|
d1.depends_on_self = d2
|
|
d2.depends_on_self = d1
|
|
d1.depends_on_self_version = d1
|
|
d2.depends_on_self_version = d2
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, setattr, d1,
|
|
'depends_on_self_version', d2)
|
|
|
|
def test_dependency_list(self):
|
|
class FooType(defs.ArtifactType):
|
|
pass
|
|
|
|
class BarType(defs.ArtifactType):
|
|
pass
|
|
|
|
class TestType(defs.ArtifactType):
|
|
depends_on = defs.ArtifactReferenceList()
|
|
depends_on_self_or_foo = defs.ArtifactReferenceList(
|
|
references=defs.ArtifactReference(['FooType', 'TestType']))
|
|
|
|
a = defs.ArtifactType(**get_artifact_fixture(id="1"))
|
|
a_copy = defs.ArtifactType(**get_artifact_fixture(id="1"))
|
|
b = defs.ArtifactType(**get_artifact_fixture(id="2"))
|
|
|
|
tt = TestType(**get_artifact_fixture(id="3"))
|
|
foo = FooType(**get_artifact_fixture(id='4'))
|
|
bar = BarType(**get_artifact_fixture(id='4'))
|
|
|
|
tt.depends_on.append(a)
|
|
tt.depends_on.append(b)
|
|
self.assertEqual([a, b], tt.depends_on)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.depends_on.append, a)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.depends_on.append, a_copy)
|
|
|
|
tt.depends_on_self_or_foo.append(tt)
|
|
tt.depends_on_self_or_foo.append(foo)
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue,
|
|
tt.depends_on_self_or_foo.append, bar)
|
|
self.assertEqual([tt, foo], tt.depends_on_self_or_foo)
|
|
|
|
def test_blob(self):
|
|
class TestType(defs.ArtifactType):
|
|
image_file = defs.BinaryObject(max_file_size=201054,
|
|
min_locations=1,
|
|
max_locations=5)
|
|
screen_shots = defs.BinaryObjectList(
|
|
objects=defs.BinaryObject(min_file_size=100), min_count=1)
|
|
|
|
tt = TestType(**get_artifact_fixture())
|
|
blob = defs.Blob()
|
|
blob.size = 1024
|
|
blob.locations.append("file://some.file.path")
|
|
tt.image_file = blob
|
|
|
|
self.assertEqual(1024, tt.image_file.size)
|
|
self.assertEqual(["file://some.file.path"], tt.image_file.locations)
|
|
|
|
def test_pre_publish_blob_validation(self):
|
|
class TestType(defs.ArtifactType):
|
|
required_blob = defs.BinaryObject(required=True)
|
|
optional_blob = defs.BinaryObject()
|
|
|
|
tt = TestType(**get_artifact_fixture())
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.__pre_publish__)
|
|
tt.required_blob = defs.Blob(size=0)
|
|
tt.__pre_publish__()
|
|
|
|
def test_pre_publish_dependency_validation(self):
|
|
class TestType(defs.ArtifactType):
|
|
required_dependency = defs.ArtifactReference(required=True)
|
|
optional_dependency = defs.ArtifactReference()
|
|
|
|
tt = TestType(**get_artifact_fixture())
|
|
self.assertRaises(exc.InvalidArtifactPropertyValue, tt.__pre_publish__)
|
|
tt.required_dependency = defs.ArtifactType(**get_artifact_fixture())
|
|
tt.__pre_publish__()
|
|
|
|
def test_default_value_of_immutable_field_in_active_state(self):
|
|
class TestType(defs.ArtifactType):
|
|
foo = defs.String(default='Bar', mutable=False)
|
|
tt = TestType(**get_artifact_fixture(state='active'))
|
|
self.assertEqual('Bar', tt.foo)
|
|
|
|
|
|
class SerTestType(defs.ArtifactType):
|
|
some_string = defs.String()
|
|
some_text = defs.Text()
|
|
some_version = defs.SemVerString()
|
|
some_int = defs.Integer()
|
|
some_numeric = defs.Numeric()
|
|
some_bool = defs.Boolean()
|
|
some_array = defs.Array()
|
|
another_array = defs.Array(
|
|
item_type=[defs.Integer(), defs.Numeric(), defs.Boolean()])
|
|
some_dict = defs.Dict()
|
|
another_dict = defs.Dict(
|
|
properties={'foo': defs.Integer(), 'bar': defs.Boolean()})
|
|
some_ref = defs.ArtifactReference()
|
|
some_ref_list = defs.ArtifactReferenceList()
|
|
some_blob = defs.BinaryObject()
|
|
some_blob_list = defs.BinaryObjectList()
|
|
|
|
|
|
class TestSerialization(test_utils.BaseTestCase):
|
|
def test_serialization_to_db(self):
|
|
ref1 = defs.ArtifactType(**get_artifact_fixture(id="1"))
|
|
ref2 = defs.ArtifactType(**get_artifact_fixture(id="2"))
|
|
ref3 = defs.ArtifactType(**get_artifact_fixture(id="3"))
|
|
|
|
blob1 = defs.Blob(size=100, locations=['http://example.com/blob1'],
|
|
item_key='some_key', checksum='abc')
|
|
blob2 = defs.Blob(size=200, locations=['http://example.com/blob2'],
|
|
item_key='another_key', checksum='fff')
|
|
blob3 = defs.Blob(size=300, locations=['http://example.com/blob3'],
|
|
item_key='third_key', checksum='123')
|
|
|
|
fixture = get_artifact_fixture()
|
|
tt = SerTestType(**fixture)
|
|
tt.some_string = 'bar'
|
|
tt.some_text = 'bazz'
|
|
tt.some_version = '11.22.33-beta'
|
|
tt.some_int = 50
|
|
tt.some_numeric = 10.341
|
|
tt.some_bool = True
|
|
tt.some_array = ['q', 'w', 'e', 'r', 't', 'y']
|
|
tt.another_array = [1, 1.2, False]
|
|
tt.some_dict = {'foobar': "FOOBAR", 'baz': "QUX"}
|
|
tt.another_dict = {'foo': 1, 'bar': True}
|
|
tt.some_ref = ref1
|
|
tt.some_ref_list = [ref2, ref3]
|
|
tt.some_blob = blob1
|
|
tt.some_blob_list = [blob2, blob3]
|
|
|
|
results = serialization.serialize_for_db(tt)
|
|
expected = fixture
|
|
expected['type_name'] = 'SerTestType'
|
|
expected['type_version'] = '1.0'
|
|
expected['properties'] = {
|
|
'some_string': {
|
|
'type': 'string',
|
|
'value': 'bar'
|
|
},
|
|
'some_text': {
|
|
'type': 'text',
|
|
'value': 'bazz'
|
|
},
|
|
'some_version': {
|
|
'type': 'string',
|
|
'value': '11.22.33-beta'
|
|
},
|
|
'some_int': {
|
|
'type': 'int',
|
|
'value': 50
|
|
},
|
|
'some_numeric': {
|
|
'type': 'numeric',
|
|
'value': 10.341
|
|
},
|
|
'some_bool': {
|
|
'type': 'bool',
|
|
'value': True
|
|
},
|
|
'some_array': {
|
|
'type': 'array',
|
|
'value': [
|
|
{
|
|
'type': 'string',
|
|
'value': 'q'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'w'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'e'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'r'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 't'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'y'
|
|
}
|
|
]
|
|
},
|
|
'another_array': {
|
|
'type': 'array',
|
|
'value': [
|
|
{
|
|
'type': 'int',
|
|
'value': 1
|
|
},
|
|
{
|
|
'type': 'numeric',
|
|
'value': 1.2
|
|
},
|
|
{
|
|
'type': 'bool',
|
|
'value': False
|
|
}
|
|
]
|
|
},
|
|
'some_dict.foobar': {
|
|
'type': 'string',
|
|
'value': 'FOOBAR'
|
|
},
|
|
'some_dict.baz': {
|
|
'type': 'string',
|
|
'value': 'QUX'
|
|
},
|
|
'another_dict.foo': {
|
|
'type': 'int',
|
|
'value': 1
|
|
},
|
|
'another_dict.bar': {
|
|
'type': 'bool',
|
|
'value': True
|
|
}
|
|
}
|
|
expected['dependencies'] = {
|
|
'some_ref': ['1'],
|
|
'some_ref_list': ['2', '3']
|
|
}
|
|
expected['blobs'] = {
|
|
'some_blob': [
|
|
{
|
|
'size': 100,
|
|
'checksum': 'abc',
|
|
'item_key': 'some_key',
|
|
'locations': ['http://example.com/blob1']
|
|
}],
|
|
'some_blob_list': [
|
|
{
|
|
'size': 200,
|
|
'checksum': 'fff',
|
|
'item_key': 'another_key',
|
|
'locations': ['http://example.com/blob2']
|
|
},
|
|
{
|
|
'size': 300,
|
|
'checksum': '123',
|
|
'item_key': 'third_key',
|
|
'locations': ['http://example.com/blob3']
|
|
}
|
|
]
|
|
}
|
|
|
|
self.assertEqual(expected, results)
|
|
|
|
def test_deserialize_from_db(self):
|
|
ts = datetime.datetime.now()
|
|
db_dict = {
|
|
"type_name": 'SerTestType',
|
|
"type_version": '1.0',
|
|
"id": "123",
|
|
"version": "11.2",
|
|
"description": None,
|
|
"name": "Foo",
|
|
"visibility": "private",
|
|
"state": "creating",
|
|
"owner": "my_tenant",
|
|
"created_at": ts,
|
|
"updated_at": ts,
|
|
"deleted_at": None,
|
|
"published_at": None,
|
|
"tags": ["test", "fixture"],
|
|
"properties": {
|
|
'some_string': {
|
|
'type': 'string',
|
|
'value': 'bar'
|
|
},
|
|
'some_text': {
|
|
'type': 'text',
|
|
'value': 'bazz'
|
|
},
|
|
'some_version': {
|
|
'type': 'string',
|
|
'value': '11.22.33-beta'
|
|
},
|
|
'some_int': {
|
|
'type': 'int',
|
|
'value': 50
|
|
},
|
|
'some_numeric': {
|
|
'type': 'numeric',
|
|
'value': 10.341
|
|
},
|
|
'some_bool': {
|
|
'type': 'bool',
|
|
'value': True
|
|
},
|
|
'some_array': {
|
|
'type': 'array',
|
|
'value': [
|
|
{
|
|
'type': 'string',
|
|
'value': 'q'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'w'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'e'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'r'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 't'
|
|
},
|
|
{
|
|
'type': 'string',
|
|
'value': 'y'
|
|
}
|
|
]
|
|
},
|
|
'another_array': {
|
|
'type': 'array',
|
|
'value': [
|
|
{
|
|
'type': 'int',
|
|
'value': 1
|
|
},
|
|
{
|
|
'type': 'numeric',
|
|
'value': 1.2
|
|
},
|
|
{
|
|
'type': 'bool',
|
|
'value': False
|
|
}
|
|
]
|
|
},
|
|
'some_dict.foobar': {
|
|
'type': 'string',
|
|
'value': 'FOOBAR'
|
|
},
|
|
'some_dict.baz': {
|
|
'type': 'string',
|
|
'value': 'QUX'
|
|
},
|
|
'another_dict.foo': {
|
|
'type': 'int',
|
|
'value': 1
|
|
},
|
|
'another_dict.bar': {
|
|
'type': 'bool',
|
|
'value': True
|
|
}
|
|
},
|
|
'blobs': {
|
|
'some_blob': [
|
|
{
|
|
'size': 100,
|
|
'checksum': 'abc',
|
|
'item_key': 'some_key',
|
|
'locations': ['http://example.com/blob1']
|
|
}],
|
|
'some_blob_list': [
|
|
{
|
|
'size': 200,
|
|
'checksum': 'fff',
|
|
'item_key': 'another_key',
|
|
'locations': ['http://example.com/blob2']
|
|
},
|
|
{
|
|
'size': 300,
|
|
'checksum': '123',
|
|
'item_key': 'third_key',
|
|
'locations': ['http://example.com/blob3']
|
|
}
|
|
]
|
|
},
|
|
'dependencies': {
|
|
'some_ref': [
|
|
{
|
|
"type_name": 'ArtifactType',
|
|
"type_version": '1.0',
|
|
"id": "1",
|
|
"version": "11.2",
|
|
"description": None,
|
|
"name": "Foo",
|
|
"visibility": "private",
|
|
"state": "creating",
|
|
"owner": "my_tenant",
|
|
"created_at": ts,
|
|
"updated_at": ts,
|
|
"deleted_at": None,
|
|
"published_at": None,
|
|
"tags": ["test", "fixture"],
|
|
"properties": {},
|
|
"blobs": {},
|
|
"dependencies": {}
|
|
}
|
|
],
|
|
'some_ref_list': [
|
|
{
|
|
"type_name": 'ArtifactType',
|
|
"type_version": '1.0',
|
|
"id": "2",
|
|
"version": "11.2",
|
|
"description": None,
|
|
"name": "Foo",
|
|
"visibility": "private",
|
|
"state": "creating",
|
|
"owner": "my_tenant",
|
|
"created_at": ts,
|
|
"updated_at": ts,
|
|
"deleted_at": None,
|
|
"published_at": None,
|
|
"tags": ["test", "fixture"],
|
|
"properties": {},
|
|
"blobs": {},
|
|
"dependencies": {}
|
|
},
|
|
{
|
|
"type_name": 'ArtifactType',
|
|
"type_version": '1.0',
|
|
"id": "3",
|
|
"version": "11.2",
|
|
"description": None,
|
|
"name": "Foo",
|
|
"visibility": "private",
|
|
"state": "creating",
|
|
"owner": "my_tenant",
|
|
"created_at": ts,
|
|
"updated_at": ts,
|
|
"deleted_at": None,
|
|
"published_at": None,
|
|
"tags": ["test", "fixture"],
|
|
"properties": {},
|
|
"blobs": {},
|
|
"dependencies": {}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
plugins_dict = {'SerTestType': [SerTestType],
|
|
'ArtifactType': [defs.ArtifactType]}
|
|
|
|
def _retrieve_plugin(name, version):
|
|
return next((p for p in plugins_dict.get(name, [])
|
|
if version and p.version == version),
|
|
plugins_dict.get(name, [None])[0])
|
|
plugins = mock.Mock()
|
|
plugins.get_class_by_typename = _retrieve_plugin
|
|
art = serialization.deserialize_from_db(db_dict, plugins)
|
|
self.assertEqual('123', art.id)
|
|
self.assertEqual('11.2', art.version)
|
|
self.assertIsNone(art.description)
|
|
self.assertEqual('Foo', art.name)
|
|
self.assertEqual('private', art.visibility)
|
|
self.assertEqual('private', art.visibility)
|
|
|
|
|
|
def get_artifact_fixture(**kwargs):
|
|
ts = datetime.datetime.now()
|
|
fixture = {
|
|
"id": "123",
|
|
"version": "11.2",
|
|
"description": None,
|
|
"name": "Foo",
|
|
"visibility": "private",
|
|
"state": "creating",
|
|
"owner": "my_tenant",
|
|
"created_at": ts,
|
|
"updated_at": ts,
|
|
"deleted_at": None,
|
|
"published_at": None,
|
|
"tags": ["test", "fixture"]
|
|
}
|
|
fixture.update(kwargs)
|
|
return fixture
|