Merge "Fix broken validatedesign endpoint"

This commit is contained in:
Zuul 2018-07-23 15:31:12 +00:00 committed by Gerrit Code Review
commit 048fb1636e
6 changed files with 294 additions and 20 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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

View 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

View File

@ -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