379 lines
13 KiB
Python
379 lines
13 KiB
Python
# 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 collections
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from kolla_kubernetes.kube_service_status import KubeResourceTypeStatus
|
|
from kolla_kubernetes.pathfinder import PathFinder
|
|
from kolla_kubernetes.utils import ExecUtils
|
|
from kolla_kubernetes.utils import JinjaUtils
|
|
from kolla_kubernetes.utils import StringUtils
|
|
from kolla_kubernetes.utils import YamlUtils
|
|
|
|
CONF = cfg.CONF
|
|
LOG = logging.getLogger()
|
|
|
|
|
|
class KollaKubernetesResources(object):
|
|
_singleton = None
|
|
|
|
_jinja_dict_cache = {}
|
|
|
|
@staticmethod
|
|
def Get():
|
|
if KollaKubernetesResources._singleton is None:
|
|
KollaKubernetesResources._singleton = KollaKubernetesResources(
|
|
PathFinder.find_config_file('service_resources.yml'))
|
|
return KollaKubernetesResources._singleton
|
|
|
|
@staticmethod
|
|
def GetJinjaDict(service_name=None, cli_args={}, debug_regex=None):
|
|
# check the cache first
|
|
cache_key = ((service_name if service_name is not None else "None") +
|
|
str(cli_args) +
|
|
(debug_regex if debug_regex is not None else "None"))
|
|
if cache_key in KollaKubernetesResources._jinja_dict_cache:
|
|
return KollaKubernetesResources._jinja_dict_cache[cache_key]
|
|
|
|
# Apply basic variables that aren't defined in any config file
|
|
jvars = {'node_config_directory': '',
|
|
'timestamp': str(time.time())}
|
|
|
|
# Add the cli args to the template vars, to be fed into templates
|
|
jvars["kolla_kubernetes"] = {
|
|
"cli": {"args": YamlUtils.yaml_dict_normalize(cli_args)}}
|
|
|
|
# Create the prioritized list of config files that need to be
|
|
# merged. Search method for config files: locks onto the first
|
|
# path where the file exists. Search method for template files:
|
|
# locks onto the first path that exists, and then expects the file
|
|
# to be there.
|
|
kolla_dir = PathFinder.find_kolla_dir()
|
|
files = [
|
|
PathFinder.find_config_file('kolla-kubernetes.yml'),
|
|
PathFinder.find_config_file('globals.yml'),
|
|
PathFinder.find_config_file('passwords.yml'),
|
|
os.path.join(kolla_dir, 'ansible/group_vars/all.yml')]
|
|
if service_name is not None:
|
|
service_ansible_file = os.path.join(
|
|
kolla_dir, 'ansible/roles', service_name, 'defaults/main.yml')
|
|
if os.path.exists(service_ansible_file):
|
|
files.append(service_ansible_file)
|
|
files.append(os.path.join(kolla_dir,
|
|
'ansible/roles/common/defaults/main.yml'))
|
|
# FIXME probably should move this stuff into
|
|
# ansible/roles/common/defaults/main.yml instead.
|
|
files.append(os.path.join(kolla_dir,
|
|
'ansible/roles/haproxy/defaults/main.yml'))
|
|
|
|
# Create the config dict
|
|
x = JinjaUtils.merge_configs_to_dict(
|
|
reversed(files), jvars, debug_regex)
|
|
|
|
# Render values containing nested jinja variables
|
|
r = JinjaUtils.dict_self_render(x)
|
|
|
|
# Add a self referential link so templates can look up things by name.
|
|
r['global'] = r
|
|
|
|
# Update the cache
|
|
KollaKubernetesResources._jinja_dict_cache[cache_key] = r
|
|
return r
|
|
|
|
def __init__(self, filename):
|
|
if not os.path.isfile(filename):
|
|
print("configuration file={} not found".format(filename))
|
|
sys.exit(1)
|
|
|
|
self.filename = filename
|
|
self.y = YamlUtils.yaml_dict_from_file(filename)
|
|
self.services = collections.OrderedDict()
|
|
for i in self.y['kolla-kubernetes']['services']:
|
|
self.services[i['name']] = Service(i)
|
|
|
|
def getServices(self):
|
|
return self.services
|
|
|
|
def getServiceByName(self, name):
|
|
r = self.getServices()
|
|
if name not in r:
|
|
print("unable to find service={}", name)
|
|
sys.exit(1)
|
|
return r[name]
|
|
|
|
def __str__(self):
|
|
s = self.__class__.__name__
|
|
for k, v in self.getServices().items():
|
|
s += "\n" + StringUtils.pad_str(" ", 2, str(v))
|
|
return s
|
|
|
|
|
|
class Service(object):
|
|
VALID_ACTIONS = 'create delete status'.split(" ")
|
|
VALID_RESOURCE_TYPES = ('configmap secret '
|
|
'disk pv pvc svc bootstrap pod').split(" ")
|
|
# Keep old logic for LEGACY support of bootstrap, run, and kill commands
|
|
# Legacy commands did not keep order. Here, we define order.
|
|
# Hoping to get rid of the LEGACY commands entirely if people okay.
|
|
# Otherwise, we wait until Ansible workflow engine.
|
|
# SVC should really be in bootstrap command, since it is stateful
|
|
# CONFIGMAP remains listed twice, since that was the old logic.
|
|
LEGACY_BOOTSTRAP_RESOURCES = ('configmap secret '
|
|
'disk pv pvc bootstrap').split(" ")
|
|
LEGACY_RUN_RESOURCES = 'configmap svc pod'.split(" ")
|
|
|
|
def __init__(self, y):
|
|
self.y = y
|
|
self.pods = collections.OrderedDict()
|
|
for i in self.y['pods']:
|
|
self.pods[i['name']] = Pod(i)
|
|
self.resourceTemplates = {}
|
|
for rt in self.VALID_RESOURCE_TYPES:
|
|
# Initialize instance resourceTemplates hash
|
|
if rt not in self.resourceTemplates:
|
|
self.resourceTemplates[rt] = []
|
|
# Skip empty definitions
|
|
if rt not in self.y['resources']:
|
|
continue
|
|
# Handle definitions
|
|
for i in self.y['resources'][rt]:
|
|
self.resourceTemplates[rt].append(ResourceTemplate(i))
|
|
|
|
def getName(self):
|
|
return self.y['name']
|
|
|
|
def getPods(self):
|
|
return self.pods
|
|
|
|
def getPodByName(self, name):
|
|
r = self.getPods()
|
|
if name not in r:
|
|
print("unable to find pod={}", name)
|
|
sys.exit(1)
|
|
return r[name]
|
|
|
|
def getResourceTemplatesByType(self, resource_type):
|
|
assert resource_type in self.resourceTemplates
|
|
return self.resourceTemplates[resource_type]
|
|
|
|
def getResourceTemplateByTypeAndName(
|
|
self, resource_type, resource_name):
|
|
|
|
# create an inverted hash[name]=resourceTemplate
|
|
resourceTemplates = self.getResourceTemplatesByType(resource_type)
|
|
h = {i.getName(): i for i in resourceTemplates}
|
|
|
|
# validate
|
|
if resource_name not in h.keys():
|
|
print("unable to find resource_name={}", resource_name)
|
|
sys.exit(1)
|
|
|
|
return h[resource_name]
|
|
|
|
def __str__(self):
|
|
s = self.__class__.__name__ + " " + self.getName()
|
|
for k, v in self.getPods().items():
|
|
s += "\n" + StringUtils.pad_str(" ", 2, str(v))
|
|
return s
|
|
|
|
def do_apply(self, action, resource_types, dry_run=False):
|
|
"""Apply action to resource_types
|
|
|
|
Example: service.apply("create", "disk")
|
|
Example: service.apply("create", ["disk", "pv", "pvc"])
|
|
Example: service.apply("delete", "all")
|
|
Example: service.apply("status", "all")
|
|
|
|
ACTION: string value of (create|delete|status)
|
|
RESOURCE_TYPES: string value of one resource type, or list of
|
|
string values of many resource types
|
|
(configmap|disk|pv|pvc|svc|bootstrap|pod).
|
|
In addition 'all' is a valid resource type.
|
|
"""
|
|
|
|
# Check action input arg for code errors
|
|
assert type(action) is str
|
|
assert action in Service.VALID_ACTIONS
|
|
|
|
# Handle resource_types as string or list, and the special case 'all'
|
|
if type(resource_types) is str:
|
|
if resource_types == 'all':
|
|
resource_types = Service.VALID_RESOURCE_TYPES
|
|
else:
|
|
resource_types = [resource_types]
|
|
|
|
# Check resource_type input arg for code errors
|
|
assert type(resource_types) is list
|
|
for t in resource_types:
|
|
assert t in Service.VALID_RESOURCE_TYPES
|
|
|
|
# If action is delete, then delete the resource types in
|
|
# reverse order.
|
|
if action == 'delete':
|
|
resource_types = reversed(resource_types)
|
|
|
|
# Execute the action for each resource_type
|
|
for rt in resource_types:
|
|
# Handle status action
|
|
if action == "status":
|
|
if rt == "disk":
|
|
raise Exception('resource type for disk not supported yet')
|
|
krs = KubeResourceTypeStatus(self, rt)
|
|
print(YamlUtils.yaml_dict_to_string(krs.asDict()))
|
|
continue
|
|
|
|
# Handle create and delete action
|
|
if rt == "configmap":
|
|
# Take care of configmap as a special case
|
|
for pod in self.getPods().values():
|
|
for container in pod.getContainers().values():
|
|
if action == 'create':
|
|
container.createConfigMaps()
|
|
elif action == 'delete':
|
|
container.deleteConfigMaps()
|
|
else:
|
|
raise Exception('Code Error')
|
|
else:
|
|
# Handle all other resource_types as the same
|
|
self._ensureResource(action, rt)
|
|
|
|
def _ensureResource(self, action, resource_type):
|
|
# Check input args
|
|
assert action in Service.VALID_ACTIONS
|
|
assert resource_type in Service.VALID_RESOURCE_TYPES
|
|
assert resource_type in self.resourceTemplates
|
|
|
|
resourceTemplates = self.resourceTemplates[resource_type]
|
|
|
|
# If action is delete, then delete the resourceTemplates in
|
|
# reverse order.
|
|
if action == 'delete':
|
|
resourceTemplates = reversed(resourceTemplates)
|
|
|
|
for resourceTemplate in resourceTemplates:
|
|
# Build the command based on if shell script or not. If
|
|
# shell script, pipe to sh. Else, pipe to kubectl
|
|
cmd = "kolla-kubernetes resource-template {} {} {} {}".format(
|
|
action, self.getName(), resource_type,
|
|
resourceTemplate.getName())
|
|
if resourceTemplate.getTemplatePath().endswith('.sh.j2'):
|
|
cmd += " | sh"
|
|
else:
|
|
cmd += " | kubectl {} -f -".format(action)
|
|
|
|
# Execute the command
|
|
ExecUtils.exec_command(cmd)
|
|
|
|
|
|
class Pod(object):
|
|
|
|
def __init__(self, y):
|
|
self.y = y
|
|
self.containers = collections.OrderedDict()
|
|
for i in self.y['containers']:
|
|
self.containers[i['name']] = Container(i)
|
|
|
|
def getName(self):
|
|
return self.y['name']
|
|
|
|
def getContainers(self):
|
|
return self.containers
|
|
|
|
def getContainerByName(self, name):
|
|
r = self.getContainers()
|
|
if name not in r:
|
|
print("unable to find container={}", name)
|
|
sys.exit(1)
|
|
return r[name]
|
|
|
|
def __str__(self):
|
|
s = self.__class__.__name__ + " " + self.getName()
|
|
for k, v in self.getContainers().items():
|
|
s += "\n" + StringUtils.pad_str(" ", 2, str(v))
|
|
return s
|
|
|
|
|
|
class Container(object):
|
|
|
|
def __init__(self, y):
|
|
self.y = y
|
|
|
|
def getName(self):
|
|
return self.y['name']
|
|
|
|
def __str__(self):
|
|
s = self.__class__.__name__
|
|
s += " " + self.getName()
|
|
return s
|
|
|
|
def createConfigMaps(self):
|
|
self._ensureConfigMaps('create')
|
|
|
|
def deleteConfigMaps(self):
|
|
self._ensureConfigMaps('delete')
|
|
|
|
def _ensureConfigMaps(self, action):
|
|
assert action in Service.VALID_ACTIONS
|
|
|
|
cmd = ("kubectl {} configmap {}-configmap".format(
|
|
action, self.getName()))
|
|
|
|
# For the create action, add some more arguments
|
|
if action == 'create':
|
|
for f in PathFinder.find_config_files(self.getName()):
|
|
cmd += ' --from-file={}={}'.format(
|
|
os.path.basename(f).replace("_", "-"), f)
|
|
|
|
# Execute the command
|
|
ExecUtils.exec_command(cmd)
|
|
|
|
|
|
class ResourceTemplate(object):
|
|
|
|
def __init__(self, y):
|
|
# Checks
|
|
assert 'template' in y, str(y) # not optional
|
|
assert 'name' in y, str(y) # not optional
|
|
# Construct
|
|
self.y = y
|
|
|
|
def getName(self):
|
|
return self.y['name']
|
|
|
|
def getTemplate(self):
|
|
return self.y['template']
|
|
|
|
def getVars(self):
|
|
return (self.y['vars']
|
|
if 'vars' in self.y else None) # optional
|
|
|
|
def getTemplatePath(self):
|
|
kkdir = PathFinder.find_kolla_kubernetes_dir()
|
|
path = os.path.join(kkdir, self.getTemplate())
|
|
assert os.path.exists(path)
|
|
return path
|
|
|
|
def __str__(self):
|
|
s = self.__class__.__name__
|
|
s += " name[{}]".format(
|
|
self.getName() if self.getName() is not None else "")
|
|
s += " template[{}]".format(self.getTemplate())
|
|
s += " vars[{}]".format(
|
|
self.getVars() if self.getVars() is not None else "")
|
|
return s
|