Skip to content

Commit

Permalink
Feature/add deployment field2metadata (#544)
Browse files Browse the repository at this point in the history
* Add 'deployment' field support in metadata.yaml

* Add tests;

* Change to use colander.MappingSchema to do validation on deployment;

* Add tests for unsupported deployment.service, deployment.type;;
  • Loading branch information
ycliuhw authored and wallyworld committed Sep 16, 2019
1 parent d58808f commit 4139c90
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 6 deletions.
76 changes: 70 additions & 6 deletions charmtools/charms.py
Expand Up @@ -17,7 +17,7 @@
from charmtools.linter import Linter
from charmtools.utils import validate_display_name

KNOWN_METADATA_KEYS = [
KNOWN_METADATA_KEYS = (
'name',
'display-name',
'summary',
Expand All @@ -39,16 +39,17 @@
'terms',
'resources',
'devices',
]
'deployment',
)

REQUIRED_METADATA_KEYS = [
REQUIRED_METADATA_KEYS = (
'name',
'summary',
]
)

KNOWN_RELATION_KEYS = ['interface', 'scope', 'limit', 'optional']
KNOWN_RELATION_KEYS = ('interface', 'scope', 'limit', 'optional')

KNOWN_SCOPES = ['global', 'container']
KNOWN_SCOPES = ('global', 'container')

TEMPLATE_PATH = os.path.abspath(os.path.dirname(__file__))

Expand Down Expand Up @@ -331,6 +332,7 @@ def proof(self):
validate_payloads(charm, lint, proof_extensions.get('payloads'))
validate_terms(charm, lint)
validate_resources(charm, lint, proof_extensions.get('resources'))
validate_deployment(charm, lint, proof_extensions.get('deployment'))

if not os.path.exists(os.path.join(charm_path, 'icon.svg')):
lint.info("No icon.svg file.")
Expand Down Expand Up @@ -562,12 +564,42 @@ def schema_type(self, **kw):
colander.String(),
name='type',
)

count = colander.SchemaNode(
colander.Integer(),
missing=1,
)


class DeploymentItem(colander.MappingSchema):
def schema_type(self, **kw):
return StrictMapping()

type_ = colander.SchemaNode(
colander.String(),
validator=colander.OneOf(['stateless', 'stateful']),
name='type',
)

service = colander.SchemaNode(
colander.String(),
validator=colander.OneOf(['loadbalancer', 'cluster', 'omit']),
name='service',
)

daemonset = colander.SchemaNode(
Boolean(),
name='daemonset',
missing=False,
)

min_version = colander.SchemaNode(
colander.String(),
name='min-version',
missing='',
)


class StorageItem(colander.MappingSchema):
def schema_type(self, **kw):
return StrictMapping()
Expand Down Expand Up @@ -701,6 +733,38 @@ def validate_resources(charm, linter, proof_extensions=None):
linter.err('resources.{}: {}'.format(k, v))


def validate_deployment(charm, linter, proof_extensions=None):
"""Validate deployment in charm metadata.
:param charm: dict of charm metadata parsed from metadata.yaml
:param linter: :class:`CharmLinter` object to which info/warning/error
messages will be written
"""

deployment = charm.get('deployment', {})
if deployment == {}:
return

if not isinstance(deployment, dict):
linter.err('deployment: must be a dict of config')
return

deployment = dict(deployment=deployment)
schema = colander.SchemaNode(colander.Mapping())
for item in deployment:
schema.add(DeploymentItem(name=item))

try:
try:
schema.deserialize(deployment)
except colander.Invalid as e:
_try_proof_extensions(e, proof_extensions)
except colander.Invalid as e:
for k, v in e.asdict().items():
linter.err('deployment.{}: {}'.format(k, v))


def validate_extra_bindings(charm, linter):
"""Validate extra-bindings in charm metadata.
Expand Down
150 changes: 150 additions & 0 deletions tests/test_charm_proof.py
Expand Up @@ -43,6 +43,7 @@
from charmtools.charms import validate_actions # noqa
from charmtools.charms import validate_terms # noqa
from charmtools.charms import validate_resources # noqa
from charmtools.charms import validate_deployment # noqa


class TestCharmProof(TestCase):
Expand Down Expand Up @@ -814,6 +815,155 @@ def test_storage_proof_extensions(self):
self.assertEqual(linter.err.call_args_list, [])


class DeploymentValidationTest(TestCase):
def test_deployment(self):
"""Charm has valid deployment."""
linter = Mock()
charm = {
'deployment': {
'type': 'stateful',
'service': 'omit',
'daemonset': True,
'min-version': "1.15.0",
}
}
validate_deployment(charm, linter)
self.assertFalse(linter.err.called)

def test_invalid_deployment(self):
"""Charm has invalid deployment."""
linter = Mock()
charm = {
'deployment': [],
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call('deployment: must be a dict of config'),
], any_order=True)

def test_deployment_unsupported_field(self):
"""Charm has the invalid deployment field."""
linter = Mock()
charm = {
'deployment': {
'type': 'stateful',
'service': 'omit',
'daemonset': True,
'min-version': "1.15.0",
'unknow-field': 'xxx',
}
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call('deployment.deployment: Unrecognized keys in mapping: "{\'unknow-field\': \'xxx\'}"'),
], any_order=True)


def test_deployment_invalid_type(self):
"""Charm has the invalid deployment type."""
linter = Mock()
charm = {
'deployment': {
'type': True,
'service': 'omit',
'daemonset': True,
'min-version': "1.15.0",
}
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call("deployment.deployment.type: True is not a string: {'type': ''}"),
], any_order=True)

def test_deployment_unsupported_type(self):
"""Charm has the unsupported deployment type."""
linter = Mock()
charm = {
'deployment': {
'type': 'foo',
'service': 'omit',
'daemonset': True,
'min-version': "1.15.0",
}
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call('deployment.deployment.type: "foo" is not one of stateless, stateful'),
], any_order=True)

def test_deployment_invalid_service(self):
"""Charm has the invalid deployment service."""
linter = Mock()
charm = {
'deployment': {
'type': 'stateful',
'service': 1,
'daemonset': True,
'min-version': "1.15.0",
}
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call("deployment.deployment.service: 1 is not a string: {'service': ''}"),
], any_order=True)

def test_deployment_unsupported_service(self):
"""Charm has the unsupported deployment service."""
linter = Mock()
charm = {
'deployment': {
'type': 'stateful',
'service': 'foo',
'daemonset': True,
'min-version': "1.15.0",
}
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call('deployment.deployment.service: "foo" is not one of loadbalancer, cluster, omit'),
], any_order=True)

def test_deployment_invalid_daemonset(self):
"""Charm has the invalid deployment daemonset."""
linter = Mock()
charm = {
'deployment': {
'type': 'stateful',
'service': 'omit',
'daemonset': 'xx',
'min-version': "1.15.0",
}
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call('deployment.deployment.daemonset: "xx" is not one of true, false'),
], any_order=True)

def test_deployment_invalid_min_version(self):
"""Charm has the invalid deployment min-version."""
linter = Mock()
charm = {
'deployment': {
'type': 'stateful',
'service': 'omit',
'daemonset': True,
'min-version': 1.15,
}
}
validate_deployment(charm, linter)
self.assertEqual(linter.err.call_count, 1)
linter.err.assert_has_calls([
call("deployment.deployment.min-version: 1.15 is not a string: {'min-version': ''}"),
], any_order=True)


class ResourcesValidationTest(TestCase):
def test_minimal_resources_config(self):
"""Charm has the minimum allowed resources configuration."""
Expand Down

0 comments on commit 4139c90

Please sign in to comment.