
This branch includes a rollup series of commits from a fork of the kubernetes repository pre 1.5 release because we didn't make the code freeze. This additional effort has been fully tested and has results submit into the gubernator to enhance confidence in this code quality vs. the single layer, posing as both master/node. To reference the gubernator results, please see: https://k8s-gubernator.appspot.com/builds/canonical-kubernetes-tests/logs/kubernetes-gce-e2e-node/ Apologies in advance for the large commit, however we did not want to submit without having successful upstream automated testing results. This commit includes: - Support for CNI networking plugins - Support for durable storage provided by ceph - Building from upstream templates (read: kubedns - no more template drift!) - An e2e charm-layer to make running validation tests much simpler/repeatable - Changes to support the 1.5.x series of kubernetes Additional note: We will be targeting -all- future work against upstream so large pull requests of this magnitude will not occur again.
203 lines
7.0 KiB
Python
203 lines
7.0 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright 2015 The Kubernetes Authors.
|
|
#
|
|
# 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 os
|
|
|
|
from charms import layer
|
|
|
|
from charms.reactive import hook
|
|
from charms.reactive import is_state
|
|
from charms.reactive import remove_state
|
|
from charms.reactive import set_state
|
|
from charms.reactive import when
|
|
from charms.reactive import when_not
|
|
|
|
from charmhelpers.core import hookenv
|
|
|
|
from shlex import split
|
|
|
|
from subprocess import call
|
|
from subprocess import check_call
|
|
from subprocess import check_output
|
|
|
|
|
|
@hook('upgrade-charm')
|
|
def reset_delivery_states():
|
|
''' Remove the state set when resources are unpacked. '''
|
|
remove_state('kubernetes-e2e.installed')
|
|
|
|
|
|
@when('kubernetes-e2e.installed')
|
|
def messaging():
|
|
''' Probe our relations to determine the propper messaging to the
|
|
end user '''
|
|
|
|
missing_services = []
|
|
if not is_state('kubernetes-master.available'):
|
|
missing_services.append('kubernetes-master')
|
|
if not is_state('certificates.available'):
|
|
missing_services.append('certificates')
|
|
|
|
if missing_services:
|
|
if len(missing_services) > 1:
|
|
subject = 'relations'
|
|
else:
|
|
subject = 'relation'
|
|
|
|
services = ','.join(missing_services)
|
|
message = 'Missing {0}: {1}'.format(subject, services)
|
|
hookenv.status_set('blocked', message)
|
|
return
|
|
|
|
hookenv.status_set('active', 'Ready to test.')
|
|
|
|
|
|
@when_not('kubernetes-e2e.installed')
|
|
def install_kubernetes_e2e():
|
|
''' Deliver the e2e and kubectl components from the binary resource stream
|
|
packages declared in the charm '''
|
|
charm_dir = os.getenv('CHARM_DIR')
|
|
arch = determine_arch()
|
|
|
|
# Get the resource via resource_get
|
|
resource = 'e2e_{}'.format(arch)
|
|
try:
|
|
archive = hookenv.resource_get(resource)
|
|
except Exception:
|
|
message = 'Error fetching the {} resource.'.format(resource)
|
|
hookenv.log(message)
|
|
hookenv.status_set('blocked', message)
|
|
return
|
|
|
|
if not archive:
|
|
hookenv.log('Missing {} resource.'.format(resource))
|
|
hookenv.status_set('blocked', 'Missing {} resource.'.format(resource))
|
|
return
|
|
|
|
# Handle null resource publication, we check if filesize < 1mb
|
|
filesize = os.stat(archive).st_size
|
|
if filesize < 1000000:
|
|
hookenv.status_set('blocked',
|
|
'Incomplete {} resource.'.format(resource))
|
|
return
|
|
|
|
hookenv.status_set('maintenance',
|
|
'Unpacking {} resource.'.format(resource))
|
|
|
|
unpack_path = '{}/files/kubernetes'.format(charm_dir)
|
|
os.makedirs(unpack_path, exist_ok=True)
|
|
cmd = ['tar', 'xfvz', archive, '-C', unpack_path]
|
|
hookenv.log(cmd)
|
|
check_call(cmd)
|
|
|
|
services = ['e2e.test', 'ginkgo', 'kubectl']
|
|
|
|
for service in services:
|
|
unpacked = '{}/{}'.format(unpack_path, service)
|
|
app_path = '/usr/local/bin/{}'.format(service)
|
|
install = ['install', '-v', unpacked, app_path]
|
|
call(install)
|
|
|
|
set_state('kubernetes-e2e.installed')
|
|
|
|
|
|
@when('tls_client.ca.saved', 'tls_client.client.certificate.saved',
|
|
'tls_client.client.key.saved', 'kubernetes-master.available',
|
|
'kubernetes-e2e.installed')
|
|
@when_not('kubeconfig.ready')
|
|
def prepare_kubeconfig_certificates(master):
|
|
''' Prepare the data to feed to create the kubeconfig file. '''
|
|
|
|
layer_options = layer.options('tls-client')
|
|
# Get all the paths to the tls information required for kubeconfig.
|
|
ca = layer_options.get('ca_certificate_path')
|
|
key = layer_options.get('client_key_path')
|
|
cert = layer_options.get('client_certificate_path')
|
|
|
|
servers = get_kube_api_servers(master)
|
|
|
|
# pedantry
|
|
kubeconfig_path = '/home/ubuntu/.kube/config'
|
|
|
|
# Create kubernetes configuration in the default location for ubuntu.
|
|
create_kubeconfig('/root/.kube/config', servers[0], ca, key, cert,
|
|
user='root')
|
|
create_kubeconfig(kubeconfig_path, servers[0], ca, key, cert,
|
|
user='ubuntu')
|
|
# Set permissions on the ubuntu users kubeconfig to ensure a consistent UX
|
|
cmd = ['chown', 'ubuntu:ubuntu', kubeconfig_path]
|
|
check_call(cmd)
|
|
|
|
set_state('kubeconfig.ready')
|
|
|
|
|
|
@when('kubernetes-e2e.installed', 'kubeconfig.ready')
|
|
def set_app_version():
|
|
''' Declare the application version to juju '''
|
|
cmd = ['kubectl', 'version', '--client']
|
|
from subprocess import CalledProcessError
|
|
try:
|
|
version = check_output(cmd).decode('utf-8')
|
|
except CalledProcessError:
|
|
message = "Missing kubeconfig causes errors. Skipping version set."
|
|
hookenv.log(message)
|
|
return
|
|
git_version = version.split('GitVersion:"v')[-1]
|
|
version_from = git_version.split('",')[0]
|
|
hookenv.application_version_set(version_from.rstrip())
|
|
|
|
|
|
def create_kubeconfig(kubeconfig, server, ca, key, certificate, user='ubuntu',
|
|
context='juju-context', cluster='juju-cluster'):
|
|
'''Create a configuration for Kubernetes based on path using the supplied
|
|
arguments for values of the Kubernetes server, CA, key, certificate, user
|
|
context and cluster.'''
|
|
# Create the config file with the address of the master server.
|
|
cmd = 'kubectl config --kubeconfig={0} set-cluster {1} ' \
|
|
'--server={2} --certificate-authority={3} --embed-certs=true'
|
|
check_call(split(cmd.format(kubeconfig, cluster, server, ca)))
|
|
# Create the credentials using the client flags.
|
|
cmd = 'kubectl config --kubeconfig={0} set-credentials {1} ' \
|
|
'--client-key={2} --client-certificate={3} --embed-certs=true'
|
|
check_call(split(cmd.format(kubeconfig, user, key, certificate)))
|
|
# Create a default context with the cluster.
|
|
cmd = 'kubectl config --kubeconfig={0} set-context {1} ' \
|
|
'--cluster={2} --user={3}'
|
|
check_call(split(cmd.format(kubeconfig, context, cluster, user)))
|
|
# Make the config use this new context.
|
|
cmd = 'kubectl config --kubeconfig={0} use-context {1}'
|
|
check_call(split(cmd.format(kubeconfig, context)))
|
|
|
|
|
|
def get_kube_api_servers(master):
|
|
'''Return the kubernetes api server address and port for this
|
|
relationship.'''
|
|
hosts = []
|
|
# Iterate over every service from the relation object.
|
|
for service in master.services():
|
|
for unit in service['hosts']:
|
|
hosts.append('https://{0}:{1}'.format(unit['hostname'],
|
|
unit['port']))
|
|
return hosts
|
|
|
|
|
|
def determine_arch():
|
|
''' dpkg wrapper to surface the architecture we are tied to'''
|
|
cmd = ['dpkg', '--print-architecture']
|
|
output = check_output(cmd).decode('utf-8')
|
|
|
|
return output.rstrip()
|