This repository has been archived by the owner on Mar 4, 2024. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of OpenStack proxy charm
- Loading branch information
0 parents
commit 158777f
Showing
9 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Overview | ||
|
||
This charm acts as a proxy to OpenStack and provides an [interface][] to apply a | ||
certain set of changes via roles, profiles, and tags to the instances of | ||
the applications that are related to this charm. | ||
|
||
## Usage | ||
|
||
When on OpenStack, this charm can be deployed, granted trust via Juju to access | ||
OpenStack, and then related to an application that supports the [interface][]. | ||
|
||
For example, [CDK][] has support for this, and can be deployed with the | ||
following bundle overlay: | ||
|
||
```yaml | ||
applications: | ||
openstack: | ||
charm: cs:~containers/openstack | ||
num_units: 1 | ||
relations: | ||
- ['openstack', 'kubernetes-master'] | ||
- ['openstack', 'kubernetes-worker'] | ||
``` | ||
|
||
Using Juju 2.4-beta1 or later: | ||
|
||
``` | ||
juju deploy cs:canonical-kubernetes --overlay ./k8s-openstack-overlay.yaml | ||
juju trust openstack | ||
``` | ||
|
||
To deploy with earlier versions of Juju, you will need to provide the cloud | ||
credentials via the `credentials`, charm config options. | ||
|
||
# Examples | ||
|
||
Following are some examples using OpenStack integration with CDK. | ||
|
||
## Creating a pod with a PersistentDisk-backed volume | ||
|
||
This script creates a busybox pod with a persistent volume claim backed by | ||
OpenStack's PersistentDisk. | ||
|
||
```sh | ||
#!/bin/bash | ||
|
||
# create a storage class using the `kubernetes.io/gce-pd` provisioner | ||
kubectl create -f - <<EOY | ||
apiVersion: storage.k8s.io/v1 | ||
kind: StorageClass | ||
metadata: | ||
name: gce-standard | ||
provisioner: kubernetes.io/gce-pd | ||
parameters: | ||
type: pd-standard | ||
EOY | ||
|
||
# create a persistent volume claim using that storage class | ||
kubectl create -f - <<EOY | ||
kind: PersistentVolumeClaim | ||
apiVersion: v1 | ||
metadata: | ||
name: testclaim | ||
spec: | ||
accessModes: | ||
- ReadWriteOnce | ||
resources: | ||
requests: | ||
storage: 100Mi | ||
storageClassName: gce-standard | ||
EOY | ||
|
||
# create the busybox pod with a volume using that PVC: | ||
kubectl create -f - <<EOY | ||
apiVersion: v1 | ||
kind: Pod | ||
metadata: | ||
name: busybox | ||
namespace: default | ||
spec: | ||
containers: | ||
- image: busybox | ||
command: | ||
- sleep | ||
- "3600" | ||
imagePullPolicy: IfNotPresent | ||
name: busybox | ||
volumeMounts: | ||
- mountPath: "/pv" | ||
name: testvolume | ||
restartPolicy: Always | ||
volumes: | ||
- name: testvolume | ||
persistentVolumeClaim: | ||
claimName: testclaim | ||
EOY | ||
``` | ||
|
||
## Creating a service with a OpenStack load-balancer | ||
|
||
The following script starts the hello-world pod behind a OpenStack-backed load-balancer. | ||
|
||
```sh | ||
#!/bin/bash | ||
|
||
kubectl run hello-world --replicas=5 --labels="run=load-balancer-example" --image=gcr.io/google-samples/node-hello:1.0 --port=8080 | ||
kubectl expose deployment hello-world --type=LoadBalancer --name=hello | ||
watch kubectl get svc -o wide --selector=run=load-balancer-example | ||
``` | ||
|
||
|
||
[interface]: https://github.com/juju-solutions/interface-openstack | ||
[CDK]: https://jujucharms.com/canonical-kubernetes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
options: | ||
credentials: | ||
description: | | ||
The base64-encoded contents of an OpenStack credentials JSON file. | ||
The credentials must contain the following keys: endpoint, username, password, | ||
user-domain-name, project-domain-name, and tenant-name. | ||
This can be used from bundles with 'include-base64://' (see | ||
https://jujucharms.com/docs/stable/charms-bundles#setting-charm-configurations-options-in-a-bundle), | ||
or from the command-line with 'juju config openstack credentials="$(base64 /path/to/file)"'. | ||
It is strongly recommended that you use 'juju trust' instead, if available. | ||
type: string | ||
default: "" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Format: http://dep.debian.net/deps/dep5/ | ||
|
||
Files: * | ||
Copyright: Copyright 2018, Canonical Ltd., All Rights Reserved. | ||
License: Apache License 2.0 | ||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
repo: 'https://github.com/juju-solutions/charm-openstack' | ||
includes: | ||
- 'layer:basic' | ||
- 'layer:status' | ||
- 'layer:snap' | ||
- 'interface:openstack' | ||
options: | ||
basic: | ||
use_venv: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import os | ||
import random | ||
import string | ||
import subprocess | ||
from base64 import b64decode | ||
|
||
import yaml | ||
from novaclient import client as novaclient_client | ||
from keystoneclient.v3 import client as keystoneclient_v3 | ||
from keystoneauth1.session import Session | ||
from keystoneauth1.identity.v3 import Password | ||
|
||
from charmhelpers.core import hookenv | ||
from charmhelpers.core.unitdata import kv | ||
|
||
from charms.layer import status | ||
|
||
|
||
# When debugging hooks, for some reason HOME is set to /home/ubuntu, whereas | ||
# during normal hook execution, it's /root. Set it here to be consistent. | ||
os.environ['HOME'] = '/root' | ||
|
||
|
||
def log(msg, *args): | ||
hookenv.log(msg.format(*args), hookenv.INFO) | ||
|
||
|
||
def log_err(msg, *args): | ||
hookenv.log(msg.format(*args), hookenv.ERROR) | ||
|
||
|
||
def get_credentials(): | ||
""" | ||
Get the credentials from either the config or the hook tool. | ||
Prefers the config so that it can be overridden. | ||
""" | ||
no_creds_msg = 'missing credentials; set credentials config' | ||
config = hookenv.config() | ||
# try to use Juju's trust feature | ||
try: | ||
result = subprocess.run(['credential-get'], | ||
check=True, | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE) | ||
creds = yaml.load(result.stdout.decode('utf8')) | ||
creds_data = creds['credential']['attributes'] | ||
_save_creds(creds_data) | ||
_create_project_user() | ||
return True | ||
except FileNotFoundError: | ||
pass # juju trust not available | ||
except subprocess.CalledProcessError as e: | ||
if 'permission denied' not in e.stderr.decode('utf8'): | ||
raise | ||
no_creds_msg = 'missing credentials access; grant with: juju trust' | ||
|
||
# try credentials config | ||
if config['credentials']: | ||
try: | ||
creds_data = b64decode(config['credentials']).decode('utf8') | ||
_save_creds(creds_data) | ||
_create_project_user() | ||
return True | ||
except Exception: | ||
status.blocked('invalid value for credentials config') | ||
return False | ||
|
||
# no creds provided | ||
status.blocked(no_creds_msg) | ||
return False | ||
|
||
|
||
def get_user_credentials(): | ||
return kv().get('charm.openstack.user-creds') | ||
|
||
|
||
def cleanup(): | ||
_delete_project_user() | ||
|
||
|
||
# Internal helpers | ||
|
||
|
||
def _save_creds(creds_data): | ||
kv().set('charm.openstack.full-creds', dict( | ||
auth_url=creds_data['endpoint'], | ||
username=creds_data['username'], | ||
password=creds_data['password'], | ||
user_domain_name=creds_data['user-domain-name'], | ||
project_domain_name=creds_data['project-domain-name'], | ||
project_name=creds_data['tenant-name'], | ||
)) | ||
|
||
|
||
def _load_creds(): | ||
return kv().get('charm.openstack.full-creds') | ||
|
||
|
||
def _get_keystone_client(): | ||
auth = Password(**_load_creds()) | ||
session = Session(auth=auth, verify=False) | ||
keystone = keystoneclient_v3.Client(session=session) | ||
keystone.auth_ref = auth.get_access(session) | ||
return keystone | ||
|
||
|
||
def _get_nova_client(): | ||
auth = Password(**_load_creds()) | ||
session = Session(auth=auth, verify=False) | ||
nova = novaclient_client.Client(2, session=session) | ||
return nova | ||
|
||
|
||
def _project_user_username(): | ||
model_uuid_prefix = os.environ['JUJU_MODEL_UUID'].split('-')[0] | ||
unit_name = hookenv.local_unit().replace('/', '-') | ||
return 'juju-charm-{}-{}'.format(model_uuid_prefix, unit_name) | ||
|
||
|
||
def _create_project_user(): | ||
""" | ||
Create a (slightly) more limited user in the project. | ||
""" | ||
client = _get_keystone_client() | ||
alphabet = string.ascii_letters + string.digits | ||
full_creds = _load_creds() | ||
username = _project_user_username() | ||
password = ''.join(random.choice(alphabet) for _ in range(20)) | ||
client.users.create( | ||
name=username, | ||
password=password, | ||
domain=full_creds['project_domain_name'], | ||
default_project=full_creds['project_name'], | ||
) | ||
kv().set('charm.openstack.user-creds', dict( | ||
auth_url=full_creds['endpoint'], | ||
username=username, | ||
password=password, | ||
user_domain_name=full_creds['user_domain_name'], | ||
project_domain_name=full_creds['project_domain_name'], | ||
project_name=full_creds['tenant_name'], | ||
)) | ||
|
||
|
||
def _delete_project_user(): | ||
client = _get_keystone_client() | ||
username = _project_user_username() | ||
client.users.delete(username) | ||
kv().unset('charm.openstack.user-creds') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
name: openstack | ||
display-name: OpenStack | ||
summary: | | ||
Proxy charm to enable OpenStack integrations via Juju relations. | ||
description: | | ||
This charm can grant select permissions to instances of applications | ||
related to it which enable integration with OpenStack specific features, | ||
such as firewalls, load balancing, block storage, object storage, etc. | ||
maintainers: ['Cory Johns <johnsca@gmail.com>'] | ||
series: | ||
- xenial | ||
tags: ['openstack', 'native', 'integration'] | ||
provides: | ||
openstack: | ||
interface: openstack |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from charms.reactive import ( | ||
hook, | ||
when_all, | ||
when_any, | ||
when_not, | ||
toggle_flag, | ||
clear_flag, | ||
) | ||
from charms.reactive.relations import endpoint_from_name | ||
|
||
from charms import layer | ||
|
||
|
||
@when_any('config.changed.credentials') | ||
def update_creds(): | ||
clear_flag('charm.openstack.creds.set') | ||
|
||
|
||
@when_not('charm.openstack.creds.set') | ||
def get_creds(): | ||
toggle_flag('charm.openstack.creds.set', layer.openstack.get_credentials()) | ||
|
||
|
||
@when_all('charm.openstack.creds.set') | ||
@when_not('endpoint.openstack.requests-pending') | ||
def no_requests(): | ||
openstack = endpoint_from_name('openstack') | ||
layer.openstack.cleanup(openstack.relation_ids) | ||
layer.status.active('ready') | ||
|
||
|
||
@when_all('charm.openstack.creds.set', | ||
'endpoint.openstack.requests-pending') | ||
def handle_requests(): | ||
openstack = endpoint_from_name('openstack') | ||
for request in openstack.requests: | ||
layer.status.maintenance( | ||
'granting request for {}'.format(request.unit_name)) | ||
if not request.has_credentials: | ||
creds = layer.openstack.get_user_credentials() | ||
request.set_credentials(creds) | ||
layer.openstack.log('Finished request for {}', request.unit_name) | ||
openstack.mark_completed() | ||
|
||
|
||
@hook('stop') | ||
def cleanup(): | ||
layer.openstack.cleanup() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
python-keystoneclient | ||
python-novaclient |