Merge "Fix broken validatedesign endpoint"
This commit is contained in:
commit
048fb1636e
@ -1,5 +1,5 @@
|
||||
from . import exceptions, logging, validation
|
||||
from .design_ref import get_documents
|
||||
from . import design_ref as dr
|
||||
import jinja2
|
||||
import jsonpath_ng
|
||||
import yaml
|
||||
@ -63,7 +63,7 @@ class Configuration:
|
||||
|
||||
@classmethod
|
||||
def from_design_ref(cls, design_ref, **kwargs):
|
||||
documents, use_dh_engine = get_documents(design_ref)
|
||||
documents, use_dh_engine = dr.get_documents(design_ref)
|
||||
|
||||
return cls(
|
||||
documents=documents,
|
||||
|
@ -26,19 +26,17 @@ LOG = logging.getLogger(__name__)
|
||||
class ValidateDesignResource(base.BaseResource):
|
||||
@policy.ApiEnforcer('kubernetes_provisioner:post_validatedesign')
|
||||
def on_post(self, req, resp):
|
||||
|
||||
result = ValidationMessage()
|
||||
try:
|
||||
json_data = self.req_json(req)
|
||||
href = json_data.get('href', None)
|
||||
config = Configuration.from_design_ref(
|
||||
href, allow_missing_substitutions=False)
|
||||
result = validation.check_design(config)
|
||||
except (exceptions.InvalidFormatError,
|
||||
exceptions.DeckhandException) as e:
|
||||
if isinstance(e, exceptions.InvalidFormatError):
|
||||
msg = "Invalid JSON Format: %s" % str(e)
|
||||
else:
|
||||
msg = str(e)
|
||||
result = ValidationMessage()
|
||||
except exceptions.InvalidFormatError as e:
|
||||
msg = "Invalid JSON Format: %s" % str(e)
|
||||
result.add_error_message(msg, name=e.title)
|
||||
return result.get_output()
|
||||
except Exception as e:
|
||||
result.add_error_message(str(e), name=e.title)
|
||||
|
||||
result.update_response(resp)
|
||||
|
@ -87,3 +87,8 @@ class ValidationMessage(object):
|
||||
:rtype: json
|
||||
"""
|
||||
return json.dumps(self.output, indent=2)
|
||||
|
||||
def update_response(self, resp):
|
||||
output = self.get_output()
|
||||
resp.status = output['code']
|
||||
resp.body = json.dumps(output)
|
||||
|
@ -26,16 +26,18 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_design(config):
|
||||
kinds = ['Docker', 'HostSystem', 'Kubelet', 'KubernetesNetwork']
|
||||
kinds = ['Docker', 'Genesis', 'HostSystem', 'Kubelet', 'KubernetesNetwork']
|
||||
validation_msg = ValidationMessage()
|
||||
for kind in kinds:
|
||||
count = 0
|
||||
schema = None
|
||||
name = None
|
||||
for doc in config.documents:
|
||||
schema = doc.get('schema', None)
|
||||
if not schema:
|
||||
msg = '"schema" is a required document key.'
|
||||
validation_msg.add_error_message(
|
||||
msg, name=exceptions.ValidationException(msg))
|
||||
exc = exceptions.ValidationException(msg)
|
||||
validation_msg.add_error_message(str(exc), name=exc.title)
|
||||
return validation_msg
|
||||
name = schema.split('/')[1]
|
||||
if name == kind:
|
||||
@ -43,11 +45,9 @@ def check_design(config):
|
||||
if count != 1:
|
||||
msg = ('There are {0} {1} documents. However, there should be one.'
|
||||
).format(count, kind)
|
||||
exc = exceptions.ValidationException(msg)
|
||||
validation_msg.add_error_message(
|
||||
msg,
|
||||
name=exceptions.ValidationException(msg),
|
||||
schema=schema,
|
||||
doc_name=kind)
|
||||
str(exc), name=exc.title, schema=schema, doc_name=kind)
|
||||
return validation_msg
|
||||
|
||||
|
||||
|
271
tests/unit/api/test_validatedesign.py
Normal file
271
tests/unit/api/test_validatedesign.py
Normal file
@ -0,0 +1,271 @@
|
||||
# Copyright 2017 AT&T Intellectual Property. All other 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 falcon import testing
|
||||
from promenade import promenade
|
||||
from promenade.control import health_api
|
||||
from unittest import mock
|
||||
import copy
|
||||
import falcon
|
||||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client():
|
||||
return testing.TestClient(promenade.start_promenade(disable='keystone'))
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def std_headers():
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'X-IDENTITY-STATUS': 'Confirmed',
|
||||
'X-USER-NAME': 'Test',
|
||||
'X-ROLES': 'admin'
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def std_body():
|
||||
return json.dumps({
|
||||
'rel': 'design',
|
||||
'href': 'http://localhost:9999',
|
||||
'type': 'application/x-yaml',
|
||||
})
|
||||
|
||||
|
||||
def test_post_validatedesign_empty_docs(client, std_body, std_headers):
|
||||
with mock.patch('promenade.design_ref.get_documents') as gd:
|
||||
gd.return_value = ([], False)
|
||||
response = client.simulate_post(
|
||||
'/api/v1.0/validatedesign', headers=std_headers, body=std_body)
|
||||
assert response.status == falcon.HTTP_400
|
||||
assert response.json['details']['errorCount'] == 5
|
||||
|
||||
|
||||
VALID_DOCS = [
|
||||
{
|
||||
'data': {
|
||||
'config': {
|
||||
'insecure-registries': ['registry:5000'],
|
||||
'live-restore': True,
|
||||
'max-concurrent-downloads': 10,
|
||||
'oom-score-adjust': -999,
|
||||
'storage-driver': 'overlay2'
|
||||
}
|
||||
},
|
||||
'metadata': {
|
||||
'layeringDefinition': {
|
||||
'abstract': False,
|
||||
'layer': 'site'
|
||||
},
|
||||
'name': 'docker',
|
||||
'schema': 'metadata/Document/v1',
|
||||
'storagePolicy': 'cleartext'
|
||||
},
|
||||
'schema': 'promenade/Docker/v1'
|
||||
},
|
||||
{
|
||||
'data': {
|
||||
'apiserver': {
|
||||
'command_prefix': [
|
||||
'/apiserver', '--authorization-mode=Node,RBAC',
|
||||
'--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds',
|
||||
'--service-cluster-ip-range=10.96.0.0/16',
|
||||
'--endpoint-reconciler-type=lease'
|
||||
]
|
||||
},
|
||||
'armada': {
|
||||
'target_manifest': 'cluster-bootstrap'
|
||||
},
|
||||
'files': [{
|
||||
'content':
|
||||
'# placeholder for triggering calico etcd bootstrapping',
|
||||
'mode':
|
||||
420,
|
||||
'path':
|
||||
'/var/lib/anchor/calico-etcd-bootstrap'
|
||||
}],
|
||||
'hostname':
|
||||
'n0',
|
||||
'images': {
|
||||
'armada': 'quay.io/airshipit/armada:master',
|
||||
'helm': {
|
||||
'tiller': 'gcr.io/kubernetes-helm/tiller:v2.7.2'
|
||||
},
|
||||
'kubernetes': {
|
||||
'apiserver':
|
||||
'gcr.io/google_containers/hyperkube-amd64:v1.10.2',
|
||||
'controller-manager':
|
||||
'gcr.io/google_containers/hyperkube-amd64:v1.10.2',
|
||||
'etcd':
|
||||
'quay.io/coreos/etcd:v3.2.14',
|
||||
'scheduler':
|
||||
'gcr.io/google_containers/hyperkube-amd64:v1.10.2'
|
||||
}
|
||||
},
|
||||
'ip':
|
||||
'192.168.77.10',
|
||||
'labels': {
|
||||
'dynamic': [
|
||||
'calico-etcd=enabled', 'coredns=enabled',
|
||||
'kubernetes-apiserver=enabled',
|
||||
'kubernetes-controller-manager=enabled',
|
||||
'kubernetes-etcd=enabled', 'kubernetes-scheduler=enabled',
|
||||
'promenade-genesis=enabled', 'ucp-control-plane=enabled'
|
||||
]
|
||||
}
|
||||
},
|
||||
'metadata': {
|
||||
'layeringDefinition': {
|
||||
'abstract': False,
|
||||
'layer': 'site'
|
||||
},
|
||||
'name': 'genesis',
|
||||
'schema': 'metadata/Document/v1'
|
||||
},
|
||||
'schema': 'promenade/Genesis/v1'
|
||||
},
|
||||
{
|
||||
'data': {
|
||||
'files': [{
|
||||
'mode':
|
||||
365,
|
||||
'path':
|
||||
'/opt/kubernetes/bin/kubelet',
|
||||
'tar_path':
|
||||
'kubernetes/node/bin/kubelet',
|
||||
'tar_url':
|
||||
'https://dl.k8s.io/v1.10.2/kubernetes-node-linux-amd64.tar.gz'
|
||||
}, {
|
||||
'content':
|
||||
'/var/lib/docker/containers/*/*-json.log\n{\n compress\n copytruncate\n create 0644 root root\n daily\n dateext\n dateformat -%Y%m%d-%s\n maxsize 10M\n missingok\n notifempty\n su root root\n rotate 1\n}',
|
||||
'mode':
|
||||
292,
|
||||
'path':
|
||||
'/etc/logrotate.d/json-logrotate'
|
||||
}],
|
||||
'images': {
|
||||
'haproxy': 'haproxy:1.8.3',
|
||||
'helm': {
|
||||
'helm': 'lachlanevenson/k8s-helm:v2.7.2'
|
||||
},
|
||||
'kubernetes': {
|
||||
'kubectl':
|
||||
'gcr.io/google_containers/hyperkube-amd64:v1.10.2'
|
||||
}
|
||||
},
|
||||
'packages': {
|
||||
'additional': ['curl', 'jq'],
|
||||
'keys': [
|
||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFWln24BEADrBl5p99uKh8+rpvqJ48u4eTtjeXAWbslJotmC/CakbNSqOb9o\nddfzRvGVeJVERt/Q/mlvEqgnyTQy+e6oEYN2Y2kqXceUhXagThnqCoxcEJ3+KM4R\nmYdoe/BJ/J/6rHOjq7Omk24z2qB3RU1uAv57iY5VGw5p45uZB4C4pNNsBJXoCvPn\nTGAs/7IrekFZDDgVraPx/hdiwopQ8NltSfZCyu/jPpWFK28TR8yfVlzYFwibj5WK\ndHM7ZTqlA1tHIG+agyPf3Rae0jPMsHR6q+arXVwMccyOi+ULU0z8mHUJ3iEMIrpT\nX+80KaN/ZjibfsBOCjcfiJSB/acn4nxQQgNZigna32velafhQivsNREFeJpzENiG\nHOoyC6qVeOgKrRiKxzymj0FIMLru/iFF5pSWcBQB7PYlt8J0G80lAcPr6VCiN+4c\nNKv03SdvA69dCOj79PuO9IIvQsJXsSq96HB+TeEmmL+xSdpGtGdCJHHM1fDeCqkZ\nhT+RtBGQL2SEdWjxbF43oQopocT8cHvyX6Zaltn0svoGs+wX3Z/H6/8P5anog43U\n65c0A+64Jj00rNDr8j31izhtQMRo892kGeQAaaxg4Pz6HnS7hRC+cOMHUU4HA7iM\nzHrouAdYeTZeZEQOA7SxtCME9ZnGwe2grxPXh/U/80WJGkzLFNcTKdv+rwARAQAB\ntDdEb2NrZXIgUmVsZWFzZSBUb29sIChyZWxlYXNlZG9ja2VyKSA8ZG9ja2VyQGRv\nY2tlci5jb20+iQI4BBMBAgAiBQJVpZ9uAhsvBgsJCAcDAgYVCAIJCgsEFgIDAQIe\nAQIXgAAKCRD3YiFXLFJgnbRfEAC9Uai7Rv20QIDlDogRzd+Vebg4ahyoUdj0CH+n\nAk40RIoq6G26u1e+sdgjpCa8jF6vrx+smpgd1HeJdmpahUX0XN3X9f9qU9oj9A4I\n1WDalRWJh+tP5WNv2ySy6AwcP9QnjuBMRTnTK27pk1sEMg9oJHK5p+ts8hlSC4Sl\nuyMKH5NMVy9c+A9yqq9NF6M6d6/ehKfBFFLG9BX+XLBATvf1ZemGVHQusCQebTGv\n0C0V9yqtdPdRWVIEhHxyNHATaVYOafTj/EF0lDxLl6zDT6trRV5n9F1VCEh4Aal8\nL5MxVPcIZVO7NHT2EkQgn8CvWjV3oKl2GopZF8V4XdJRl90U/WDv/6cmfI08GkzD\nYBHhS8ULWRFwGKobsSTyIvnbk4NtKdnTGyTJCQ8+6i52s+C54PiNgfj2ieNn6oOR\n7d+bNCcG1CdOYY+ZXVOcsjl73UYvtJrO0Rl/NpYERkZ5d/tzw4jZ6FCXgggA/Zxc\njk6Y1ZvIm8Mt8wLRFH9Nww+FVsCtaCXJLP8DlJLASMD9rl5QS9Ku3u7ZNrr5HWXP\nHXITX660jglyshch6CWeiUATqjIAzkEQom/kEnOrvJAtkypRJ59vYQOedZ1sFVEL\nMXg2UCkD/FwojfnVtjzYaTCeGwFQeqzHmM241iuOmBYPeyTY5veF49aBJA1gEJOQ\nTvBR8Q==\n=Fm3p\n-----END PGP PUBLIC KEY BLOCK-----'
|
||||
],
|
||||
'repositories':
|
||||
['deb http://apt.dockerproject.org/repo ubuntu-xenial main'],
|
||||
'required': {
|
||||
'docker': 'docker-engine=1.13.1-0~ubuntu-xenial',
|
||||
'socat': 'socat=1.7.3.1-1'
|
||||
}
|
||||
},
|
||||
'validation': {
|
||||
'pod_logs': {
|
||||
'image': 'busybox:1.28.3'
|
||||
}
|
||||
}
|
||||
},
|
||||
'metadata': {
|
||||
'layeringDefinition': {
|
||||
'abstract': False,
|
||||
'layer': 'site'
|
||||
},
|
||||
'name': 'host-system',
|
||||
'schema': 'metadata/Document/v1'
|
||||
},
|
||||
'schema': 'promenade/HostSystem/v1'
|
||||
},
|
||||
{
|
||||
'data': {
|
||||
'arguments': [
|
||||
'--cni-bin-dir=/opt/cni/bin', '--cni-conf-dir=/etc/cni/net.d',
|
||||
'--eviction-max-pod-grace-period=-1', '--network-plugin=cni',
|
||||
'--node-status-update-frequency=5s',
|
||||
'--serialize-image-pulls=false', '--v=5'
|
||||
],
|
||||
'images': {
|
||||
'pause': 'gcr.io/google_containers/pause-amd64:3.0'
|
||||
}
|
||||
},
|
||||
'metadata': {
|
||||
'layeringDefinition': {
|
||||
'abstract': False,
|
||||
'layer': 'site'
|
||||
},
|
||||
'name': 'kubelet',
|
||||
'schema': 'metadata/Document/v1',
|
||||
'storagePolicy': 'cleartext'
|
||||
},
|
||||
'schema': 'promenade/Kubelet/v1'
|
||||
},
|
||||
{
|
||||
'data': {
|
||||
'dns': {
|
||||
'bootstrap_validation_checks': [
|
||||
'calico-etcd.kube-system.svc.cluster.local', 'google.com',
|
||||
'kubernetes-etcd.kube-system.svc.cluster.local',
|
||||
'kubernetes.default.svc.cluster.local'
|
||||
],
|
||||
'cluster_domain':
|
||||
'cluster.local',
|
||||
'service_ip':
|
||||
'10.96.0.10',
|
||||
'upstream_servers': ['8.8.8.8', '8.8.4.4']
|
||||
},
|
||||
'etcd': {
|
||||
'container_port': 2379,
|
||||
'haproxy_port': 2378
|
||||
},
|
||||
'hosts_entries': [{
|
||||
'ip': '192.168.77.1',
|
||||
'names': ['registry']
|
||||
}],
|
||||
'kubernetes': {
|
||||
'apiserver_port': 6443,
|
||||
'haproxy_port': 6553,
|
||||
'pod_cidr': '10.97.0.0/16',
|
||||
'service_cidr': '10.96.0.0/16',
|
||||
'service_ip': '10.96.0.1'
|
||||
}
|
||||
},
|
||||
'metadata': {
|
||||
'layeringDefinition': {
|
||||
'abstract': False,
|
||||
'layer': 'site'
|
||||
},
|
||||
'name': 'kubernetes-network',
|
||||
'schema': 'metadata/Document/v1',
|
||||
'storagePolicy': 'cleartext'
|
||||
},
|
||||
'schema': 'promenade/KubernetesNetwork/v1'
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_post_validatedesign_valid_docs(client, std_body, std_headers):
|
||||
with mock.patch('promenade.design_ref.get_documents') as gd:
|
||||
gd.return_value = (VALID_DOCS, False)
|
||||
response = client.simulate_post(
|
||||
'/api/v1.0/validatedesign', headers=std_headers, body=std_body)
|
||||
assert response.status == falcon.HTTP_200
|
||||
assert response.json['details']['errorCount'] == 0
|
4
tox.ini
4
tox.ini
@ -11,7 +11,7 @@ setenv =
|
||||
deps = -r{toxinidir}/requirements-frozen.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
pytest
|
||||
pytest {posargs}
|
||||
|
||||
[testenv:py36]
|
||||
setenv =
|
||||
@ -19,7 +19,7 @@ setenv =
|
||||
deps = -r{toxinidir}/requirements-frozen.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
pytest
|
||||
pytest {posargs}
|
||||
|
||||
[testenv:bandit]
|
||||
deps = bandit==1.4.0
|
||||
|
Loading…
Reference in New Issue
Block a user