Merge "Add primitive support for specifying replicas"

This commit is contained in:
Jenkins 2016-09-21 15:35:09 +00:00 committed by Gerrit Code Review
commit cfa3ead967
5 changed files with 112 additions and 34 deletions

View File

@ -9,6 +9,7 @@ from fuel_ccp.config import cli
from fuel_ccp.config import images
from fuel_ccp.config import kubernetes
from fuel_ccp.config import registry
from fuel_ccp.config import replicas
from fuel_ccp.config import repositories
LOG = logging.getLogger(__name__)
@ -57,7 +58,8 @@ def get_config_defaults():
'verbose_level': 1,
'log_file': None,
})
for module in [cli, builder, images, kubernetes, registry, repositories]:
for module in [cli, builder, images, kubernetes, registry, replicas,
repositories]:
defaults._merge(module.DEFAULTS)
return defaults
@ -72,7 +74,8 @@ def get_config_schema():
'log_file': {'anyOf': [{'type': 'null'}, {'type': 'string'}]},
},
}
for module in [cli, builder, images, kubernetes, registry, repositories]:
for module in [cli, builder, images, kubernetes, registry, replicas,
repositories]:
schema['properties'].update(module.SCHEMA)
# Don't validate all options used to be added from oslo.log and oslo.config
ignore_opts = ['debug', 'verbose', 'log_file']

View File

@ -0,0 +1,12 @@
DEFAULTS = {
}
SCHEMA = {
'replicas': {
'type': 'object',
"additionalProperties": {
"type": "integer",
"minimum": 1,
},
},
}

View File

@ -3,6 +3,7 @@ import json
import logging
import os
import re
import six
from fuel_ccp.common import jinja_utils
from fuel_ccp.common import utils
@ -59,19 +60,21 @@ def _get_service_files_hash(service_dir, files, configs):
def parse_role(service_dir, role, config):
service = role["service"]
if service["name"] not in config.get("topology", {}):
service_name = service["name"]
if service_name not in config.get("topology", {}):
LOG.info("Service %s not in topology config, skipping deploy",
service["name"])
service_name)
return
LOG.info("Scheduling service %s deployment", service["name"])
LOG.info("Scheduling service %s deployment", service_name)
_expand_files(service, role.get("files"))
files_cm = _create_files_configmap(
service_dir, service["name"], role.get("files"))
service_dir, service_name, role.get("files"))
meta_cm = _create_meta_configmap(service)
workflows = _parse_workflows(service)
workflow_cm = _create_workflow(workflows, service["name"])
workflow_cm = _create_workflow(workflows, service_name)
configmaps = config['configmaps'] + (files_cm, meta_cm, workflow_cm)
if CONF.action.dry_run:
@ -91,16 +94,26 @@ def parse_role(service_dir, role, config):
cont_spec = templates.serialize_daemon_pod_spec(service)
affinity = templates.serialize_affinity(service, config["topology"])
replicas = config.get("replicas", {}).get(service_name)
if service.get("daemonset", False):
obj = templates.serialize_daemonset(service["name"], cont_spec,
if replicas is not None:
LOG.error("Replicas was specified for %s, but it's implemented "
"using Kubernetes DaemonSet that will deploy service on "
"all matching nodes (section 'nodes' in config file)",
service_name)
raise RuntimeError("Replicas couldn't be specified for services "
"implemented using Kubernetes DaemonSet")
obj = templates.serialize_daemonset(service_name, cont_spec,
affinity)
else:
obj = templates.serialize_deployment(service["name"], cont_spec,
affinity)
replicas = replicas or 1
obj = templates.serialize_deployment(service_name, cont_spec,
affinity, replicas)
kubernetes.process_object(obj)
_create_service(service)
LOG.info("Service %s successfuly scheduled", service["name"])
LOG.info("Service %s successfuly scheduled", service_name)
def _parse_workflows(service):
@ -291,7 +304,7 @@ def _create_meta_configmap(service):
return kubernetes.process_object(template)
def _make_topology(nodes, roles):
def _make_topology(nodes, roles, replicas):
failed = False
# TODO(sreshetniak): move it to validation
if not nodes:
@ -303,6 +316,9 @@ def _make_topology(nodes, roles):
if failed:
raise RuntimeError("Failed to create topology for services")
# Replicas are optional, 1 replica will deployed by default
replicas = replicas or dict()
# TODO(sreshetniak): add validation
k8s_nodes = kubernetes.list_k8s_nodes()
k8s_node_names = kubernetes.get_object_names(k8s_nodes)
@ -330,6 +346,26 @@ def _make_topology(nodes, roles):
service_to_node[svc].extend(roles_to_node[role])
else:
LOG.warning("Role '%s' defined, but unused", role)
replicas = replicas.copy()
for svc, svc_hosts in six.iteritems(service_to_node):
svc_replicas = replicas.pop(svc, None)
if svc_replicas is None:
continue
svc_hosts_count = len(svc_hosts)
if svc_replicas > svc_hosts_count:
LOG.error("Requested %s replicas for %s while only %s hosts able "
"to run that service (%s)", svc_replicas, svc,
svc_hosts_count, ", ".join(svc_hosts))
raise RuntimeError("Replicas doesn't match available hosts.")
if replicas:
LOG.error("Replicas defined for unspecified service(s): %s",
", ".join(replicas.keys()))
raise RuntimeError("Replicas defined for unspecified service(s)")
return service_to_node
@ -365,9 +401,11 @@ def deploy_components(components_map, components):
if CONF.action.export_dir:
os.makedirs(os.path.join(CONF.action.export_dir, 'configmaps'))
config = utils.get_global_parameters("configs", "nodes", "roles")
config = utils.get_global_parameters("configs", "nodes", "roles",
"replicas")
config["topology"] = _make_topology(config.get("nodes"),
config.get("roles"))
config.get("roles"),
config.get("replicas"))
namespace = CONF.kubernetes.namespace
_create_namespace(namespace)

View File

@ -260,7 +260,7 @@ def serialize_job(name, spec):
}
def serialize_deployment(name, spec, affinity):
def serialize_deployment(name, spec, affinity, replicas):
return {
"apiVersion": "extensions/v1beta1",
"kind": "Deployment",
@ -268,7 +268,7 @@ def serialize_deployment(name, spec, affinity):
"name": name
},
"spec": {
"replicas": 1,
"replicas": replicas,
"strategy": {
"rollingUpdate": {
"maxSurge": 1,

View File

@ -321,20 +321,11 @@ class TestDeployMakeTopology(base.TestCase):
self.useFixture(
fixtures.MockPatch("fuel_ccp.kubernetes.list_k8s_nodes"))
def test_make_empty_topology(self):
self.assertRaises(RuntimeError,
deploy._make_topology, None, None)
self.assertRaises(RuntimeError,
deploy._make_topology, None, {"spam": "eggs"})
self.assertRaises(RuntimeError,
deploy._make_topology, {"spam": "eggs"}, None)
def test_make_topology(self):
node_list = ["node1", "node2", "node3"]
self.useFixture(fixtures.MockPatch(
"fuel_ccp.kubernetes.get_object_names", return_value=node_list))
roles = {
self._roles = {
"controller": [
"mysql",
"keystone"
@ -345,6 +336,15 @@ class TestDeployMakeTopology(base.TestCase):
]
}
def test_make_empty_topology(self):
self.assertRaises(RuntimeError,
deploy._make_topology, None, None, None)
self.assertRaises(RuntimeError,
deploy._make_topology, None, {"spam": "eggs"}, None)
self.assertRaises(RuntimeError,
deploy._make_topology, {"spam": "eggs"}, None, None)
def test_make_topology_without_replicas(self):
nodes = {
"node1": {
"roles": ["controller"]
@ -361,10 +361,10 @@ class TestDeployMakeTopology(base.TestCase):
"libvirtd": ["node2", "node3"]
}
topology = deploy._make_topology(nodes, roles)
topology = deploy._make_topology(nodes, self._roles, None)
self.assertDictEqual(expected_topology, topology)
# check if role is defined but not used
def test_make_topology_without_replicas_unused_role(self):
nodes = {
"node1": {
"roles": ["controller"]
@ -375,11 +375,11 @@ class TestDeployMakeTopology(base.TestCase):
"mysql": ["node1"],
"keystone": ["node1"]
}
topology = deploy._make_topology(nodes, roles)
topology = deploy._make_topology(nodes, self._roles, None)
self.assertDictEqual(expected_topology, topology)
# two ways to define topology that should give the same result
# first
def test_make_topology_without_replicas_twice_used_role(self):
nodes = {
"node1": {
"roles": ["controller", "compute"]
@ -395,10 +395,10 @@ class TestDeployMakeTopology(base.TestCase):
"nova-compute": ["node1", "node2", "node3"],
"libvirtd": ["node1", "node2", "node3"]
}
topology = deploy._make_topology(nodes, roles)
topology = deploy._make_topology(nodes, self._roles, None)
self.assertDictEqual(expected_topology, topology)
# second
def test_make_topology_without_replicas_twice_used_node(self):
nodes = {
"node1": {
"roles": ["controller"]
@ -414,5 +414,30 @@ class TestDeployMakeTopology(base.TestCase):
"nova-compute": ["node1", "node2", "node3"],
"libvirtd": ["node1", "node2", "node3"]
}
topology = deploy._make_topology(nodes, roles)
topology = deploy._make_topology(nodes, self._roles, None)
self.assertDictEqual(expected_topology, topology)
def test_make_topology_replicas_bigger_than_nodes(self):
replicas = {
"keystone": 2
}
nodes = {
"node1": {
"roles": ["controller"]
}
}
self.assertRaises(RuntimeError,
deploy._make_topology, nodes, self._roles, replicas)
def test_make_topology_unspecified_service_replicas(self):
replicas = {
"foobar": 42
}
nodes = {}
self.assertRaises(RuntimeError,
deploy._make_topology, nodes, self._roles, replicas)