Initial commit

This commit is contained in:
Gabriel Adrian Samfira
2020-09-01 15:11:38 +00:00
commit e79e652654
27 changed files with 2805 additions and 0 deletions

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Ironic API juju charm

80
config.yaml Normal file
View File

@@ -0,0 +1,80 @@
options:
openstack-origin:
default: distro
type: string
description: |
Repository from which to install. May be one of the following:
distro (default), ppa:somecustom/ppa, a deb url sources entry,
or a supported Ubuntu Cloud Archive e.g.
.
cloud:<series>-<openstack-release>
cloud:<series>-<openstack-release>/updates
cloud:<series>-<openstack-release>/staging
cloud:<series>-<openstack-release>/proposed
.
See https://wiki.ubuntu.com/OpenStack/CloudArchive for info on which
cloud archives are available and supported.
rabbit-user:
default: ironic
type: string
description: Username used to access rabbitmq queue
rabbit-vhost:
default: openstack
type: string
description: Rabbitmq vhost
database-user:
default: ironic
type: string
description: Username for Ironic database access
database:
default: ironic
type: string
description: Database name for Ironic
debug:
default: False
type: boolean
description: Enable debug logging
verbose:
default: False
type: boolean
description: Enable verbose logging
region:
default: RegionOne
type: string
description: OpenStack Region
use-ipxe:
default: false
type: boolean
description: |
Use iPXE instead of PXE. This option will install an aditional
HTTP server with a root in /httpboot.
ipxe-http-port:
default: "8080"
type: string
description: |
The port used for the HTTP server used to serve iPXE resources.
max-tftp-block-size:
default: 0
type: int
description: |
Force TFTP server maximum block size. Setting this option to anything
other than 0, will force the block size sent over TFTP to the value
specified here. Valid range is 512-65535. By default, clients will
negotiate the block size.
Use this option if you're running ironic in a network with lower
MTU. The value of this option should be 32 bits less than the MTU.
If your MTU is 1450, the value for this option should be 1418.
disable-secure-erase:
default: false
type: boolean
description: |
This will disable secure erase in Ironic, when releasing a node. An ATA
Secure Erase will be attempted. If it's not supported, the disks will be
shreaded by writting random data to them once, then overwriting that data
with zeros.
Enabling this option will preserve the data on disk after release (not
recommended for production).

1950
icon.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 81 KiB

10
layer.yaml Normal file
View File

@@ -0,0 +1,10 @@
includes:
- layer:openstack-principle
- interface:mysql-shared
- interface:rabbitmq
- interface:keystone-credentials
repo: https://github.com/gabriel-samfira/charm-ironic-conductor
options:
basic:
use_venv: true
include_system_packages: true

0
lib/__init__.py Normal file
View File

0
lib/charm/__init__.py Normal file
View File

View File

View File

View File

@@ -0,0 +1,178 @@
import os
import shutil
from charmhelpers.core.templating import render
import charmhelpers.core.host as ch_host
import charmhelpers.fetch as fetch
import charmhelpers.core.hookenv as hookenv
_IRONIC_USER = "ironic"
_IRONIC_GROUP = "ironic"
class PXEBootBase(object):
TFTP_ROOT = "/tftpboot"
HTTP_ROOT = "/httpboot"
HTTP_SERVER_CONF = "/etc/nginx/nginx.conf"
GRUB_DIR = os.path.join(TFTP_ROOT, "grub")
GRUB_CFG = os.path.join(GRUB_DIR, "grub.cfg")
MAP_FILE = os.path.join(TFTP_ROOT, "map-file")
TFTP_CONFIG = "/etc/default/tftpd-hpa"
# This is a file map of source to destination. The destination is
# relative to self.TFTP_ROOT
FILE_MAP = {
"/usr/lib/PXELINUX/pxelinux.0": "pxelinux.0",
"/usr/lib/syslinux/modules/bios/chain.c32": "chain.c32",
"/usr/lib/syslinux/modules/bios/ldlinux.c32": "ldlinux.c32",
"/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed": "grubx64.efi",
"/usr/lib/shim/shim.efi.signed": "bootx64.efi",
"/usr/lib/ipxe/undionly.kpxe": "undionly.kpxe",
"/usr/lib/ipxe/ipxe.efi": "ipxe.efi",
}
TFTP_PACKAGES = ["tftpd-hpa"]
TFTPD_SERVICE = "tftpd-hpa"
PACKAGES = [
'syslinux-common',
'pxelinux',
'grub-efi-amd64-signed',
'shim-signed',
'ipxe',
]
HTTPD_PACKAGES = ["nginx"]
HTTPD_SERVICE_NAME = "nginx"
IRONIC_USER = _IRONIC_USER
IRONIC_GROUP = _IRONIC_GROUP
def __init__(self, charm_config):
self._config = charm_config
def get_restart_map(self):
return {
self.TFTP_CONFIG: [self.TFTPD_SERVICE,],
self.MAP_FILE: [self.TFTPD_SERVICE,],
self.GRUB_CFG: [self.TFTPD_SERVICE,],
}
def determine_packages(self):
default_packages = self.PACKAGES + self.TFTP_PACKAGES
use_ipxe = self._config.get('use-ipxe', False)
hookenv.log(">>>>>>>>> USE IPXE: %r --> %r" % (use_ipxe, self._config))
if use_ipxe:
default_packages.extend(self.HTTPD_PACKAGES)
return default_packages
def _copy_resources(self):
self._ensure_folders()
for f in self.FILE_MAP:
if os.path.isfile(f) is False:
raise ValueError(
"Missing required file %s. Package not installes?" % f)
shutil.copy(
f, os.path.join(self.TFTP_ROOT, self.FILE_MAP[f]),
follow_symlinks=True)
ch_host.chownr(
self.TFTP_ROOT, _IRONIC_USER, _IRONIC_GROUP, chowntopdir=True)
def _ensure_folders(self):
if os.path.isdir(self.TFTP_ROOT) is False:
os.makedirs(self.TFTP_ROOT)
if os.path.isdir(self.HTTP_ROOT) is False:
os.makedirs(self.HTTP_ROOT)
if os.path.isdir(self.GRUB_DIR) is False:
os.makedirs(self.GRUB_DIR)
ch_host.chownr(
self.TFTP_ROOT, _IRONIC_USER, _IRONIC_GROUP, chowntopdir=True)
ch_host.chownr(
self.HTTP_ROOT, _IRONIC_USER, _IRONIC_GROUP, chowntopdir=True)
# def _create_file_map(self):
# self._ensure_folders()
# render(source='tftp-file-map',
# target=self.MAP_FILE,
# owner=_IRONIC_USER,
# perms=0o664,
# context={})
# def _create_grub_cfg(self):
# self._ensure_folders()
# render(source='grub-efi',
# target=os.path.join(self.GRUB_DIR, "grub.cfg"),
# owner="root",
# perms=0o644,
# context={
# "tftpboot": self.TFTP_ROOT,
# })
# def _create_tftp_config(self):
# cfg_dir = os.path.dirname(self.TFTP_CONFIG)
# if os.path.isdir(cfg_dir) is False:
# raise Exception("Could not find %s" % cfg_dir)
# render(source='tftpd-hpa',
# target=self.TFTP_CONFIG,
# owner="root",
# perms=0o644,
# context={
# "tftpboot": self.TFTP_ROOT,
# "max_tftp_block_size": self._config.get(
# "max_tftp_block_size", 0)
# })
# def _enable_ipxe_webserver(self):
# if self._config.get("use_ipxe", False) is False:
# return
# fetch.apt_install(self.HTTPD_PACKAGES, fatal=True)
# render(source='nginx.conf',
# target=self.HTTP_SERVER_CONF,
# owner="root",
# perms=0o644,
# context={
# "ironic_user": _IRONIC_USER,
# "ipxe_http_port": self._config.get(
# "ipxe_http_port", 8080),
# "httpboot": self.HTTP_ROOT,
# })
# def configure_resources(self):
# # On Ubuntu 20.04, if IPv6 is not available on the system,
# # the tftp-hpa package fails to install properly. We create the
# # config beforehand, forcing IPv4.
# self._create_tftp_config()
# self._create_file_map()
# fetch.apt_install(self.TFTP_PACKAGES, fatal=True)
# fetch.apt_install(self.PACKAGES, fatal=True)
# self._copy_resources()
# self._create_grub_cfg()
class PXEBootBionic(PXEBootBase):
# This is a file map of source to destination. The destination is
# relative to self.TFTP_ROOT
FILE_MAP = {
"/usr/lib/PXELINUX/pxelinux.0": "pxelinux.0",
"/usr/lib/syslinux/modules/bios/chain.c32": "chain.c32",
"/usr/lib/syslinux/modules/bios/ldlinux.c32": "ldlinux.c32",
"/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed": "grubx64.efi",
"/usr/lib/shim/shimx64.efi.signed": "bootx64.efi",
"/usr/lib/ipxe/undionly.kpxe": "undionly.kpxe",
"/usr/lib/ipxe/ipxe.efi": "ipxe.efi",
}
def get_pxe_config_class(charm_config):
# In the future, we may need to make slight adjustments to package
# names and/or configuration files, based on the version of Ubuntu
# we are installing on. This function serves as a factory which will
# return an instance of the proper class to the charm. For now we only
# have one class.
series = ch_host.get_distrib_codename()
if series == "bionic":
return PXEBootBionic(charm_config)
return PXEBootBase(charm_config)

View File

@@ -0,0 +1,167 @@
from __future__ import absolute_import
import collections
import os
import charms_openstack.charm
import charms_openstack.adapters
import charms_openstack.ip as os_ip
from charms_openstack.adapters import (
RabbitMQRelationAdapter,
DatabaseRelationAdapter,
OpenStackRelationAdapters,
)
import charm.openstack.ironic.controller_utils as controller_utils
PACKAGES = [
'ironic-conductor',
'python-mysqldb',
'python3-dracclient',
'python3-sushy',
'python3-ironicclient',
'python3-scciclient',
'shellinabox',
'openssl',
'socat',
'open-iscsi',
'qemu-utils',
'ipmitool']
IRONIC_DIR = "/etc/ironic/"
IRONIC_CONF = os.path.join(IRONIC_DIR, "ironic.conf")
ROOTWRAP_CONF = os.path.join(IRONIC_DIR, "rootwrap.conf")
FILTERS_DIR = os.path.join(IRONIC_DIR, "rootwrap.d")
IRONIC_LIB_FILTERS = os.path.join(
FILTERS_DIR, "ironic-lib.filters")
IRONIC_UTILS_FILTERS = os.path.join(
FILTERS_DIR, "ironic-utils.filters")
TFTP_CONF = "/etc/default/tftpd-hpa"
HTTP_SERVER_CONF = "/etc/nginx/nginx.conf"
OPENSTACK_RELEASE_KEY = 'ironic-charm.openstack-release-version'
# select the default release function
charms_openstack.charm.use_defaults('charm.default-select-release')
def restart_all():
IronicConductorCharm.singleton.restart_all()
def assess_status():
IronicConductorCharm.singleton.assess_status()
def request_endpoint_information(keystone):
charm = IronicConductorCharm.singleton
keystone.request_credentials(
charm.name, region=charm.region)
def request_amqp_access(amqp):
charm = IronicConductorCharm.singleton
user, vhost = charm.get_amqp_credentials()
amqp.request_access(username=user, vhost=vhost)
def setup_database(database):
charm = IronicConductorCharm.singleton
for db in charm.get_database_setup():
database.configure(**db)
class IronicAdapters(OpenStackRelationAdapters):
relation_adapters = {
'amqp': RabbitMQRelationAdapter,
'shared_db': DatabaseRelationAdapter,
}
class IronicConductorCharm(charms_openstack.charm.OpenStackCharm):
adapters_class = IronicAdapters
abstract_class = False
release = 'train'
name = 'ironic'
packages = PACKAGES
service_type = 'ironic'
default_service = 'ironic-conductor'
services = ['ironic-conductor', 'tftpd-hpa']
required_relations = [
'shared-db', 'amqp', 'identity-credentials']
restart_map = {
IRONIC_CONF: ['ironic-conductor', ],
IRONIC_UTILS_FILTERS: ['ironic-conductor', ],
IRONIC_LIB_FILTERS: ['ironic-conductor', ],
ROOTWRAP_CONF: ['ironic-conductor', ],
}
# Package for release version detection
release_pkg = 'ironic-common'
# Package codename map for ironic-common
package_codenames = {
'ironic-common': collections.OrderedDict([
('13', 'train'),
('15', 'ussuri'),
]),
}
group = "ironic"
def __init__(self, **kw):
super().__init__(**kw)
self.pxe_config = controller_utils.get_pxe_config_class(
self.config)
self.packages.extend(self.pxe_config.determine_packages())
self._configure_ipxe_webserver()
self.config["tftpboot"] = self.pxe_config.TFTP_ROOT
self.config["httpboot"] = self.pxe_config.HTTP_ROOT
self.config["ironic_user"] = self.pxe_config.IRONIC_USER
self.config["ironic_group"] = self.pxe_config.IRONIC_GROUP
self.restart_map.update(self.pxe_config.get_restart_map())
def _configure_ipxe_webserver(self):
if self.config.get('use-ipxe', False) is False:
self.purge_packages.extend(self.pxe_config.HTTPD_PACKAGES)
return
httpd_svc_name = self.pxe_config.HTTPD_SERVICE_NAME
self.services.append(httpd_svc_name)
self.restart_map[self.pxe_config.HTTP_SERVER_CONF] = [httpd_svc_name,]
def upgrade_charm(self):
self.install()
super().upgrade_charm()
self.assess_status()
def config_changed(self):
self.install()
self.pxe_config._copy_resources()
super().config_changed()
self.assess_status()
def install(self):
self.configure_source()
super().install()
self.assess_status()
def get_amqp_credentials(self):
"""Provide the default amqp username and vhost as a tuple.
:returns (username, host): two strings to send to the amqp provider.
"""
return (self.config['rabbit-user'], self.config['rabbit-vhost'])
def get_database_setup(self):
return [
dict(
database=self.config['database'],
username=self.config['database-user'], )
]

30
metadata.yaml Normal file
View File

@@ -0,0 +1,30 @@
name: ironic-conductor
summary: Openstack bare metal conductor
maintainer: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
description: |
OpenStack bare metal provisioning a.k.a Ironic is an integrated OpenStack
program which aims to provision bare metal machines instead of virtual
machines, forked from the Nova baremetal driver. It is best thought of
as a bare metal hypervisor API and a set of plugins which interact with
the bare metal hypervisors. By default, it will use PXE and IPMI in order
to provision and turn on/off machines, but Ironic also supports
vendor-specific plugins which may implement additional functionality.
tags:
- openstack
- baremetal
series:
- bionic
- focal
extra-bindings:
deployment:
cleaning:
internal:
subordinate: false
display-name: Ironic Conductor
requires:
shared-db:
interface: mysql-shared
amqp:
interface: rabbitmq
identity-credentials:
interface: keystone-credentials

0
reactive/__init__.py Normal file
View File

View File

@@ -0,0 +1,58 @@
from __future__ import absolute_import
import charms.reactive as reactive
import charmhelpers.core.hookenv as hookenv
import charms_openstack.charm as charm
import charm.openstack.ironic.ironic as ironic # noqa
from charmhelpers.core.templating import render
import charmhelpers.contrib.network.ip as ch_ip
import charms_openstack.adapters as adapters
# Use the charms.openstack defaults for common states and hooks
charm.use_defaults(
'charm.installed',
'upgrade-charm',
'config.changed',
'update-status')
@reactive.when('shared-db.available')
@reactive.when('identity-credentials.available')
@reactive.when('amqp.available')
def render_stuff(*args):
hookenv.log("about to call the render_configs with {}".format(args))
with charm.provide_charm_instance() as ironic_charm:
ironic_charm.render_with_interfaces(
charm.optional_interfaces(args))
ironic_charm.assess_status()
reactive.set_state('config.complete')
@reactive.when('identity-credentials.connected')
def setup_endpoint(keystone):
ironic.request_endpoint_information(keystone)
ironic.assess_status()
@reactive.when('amqp.connected')
def request_amqp_access(amqp):
ironic.request_amqp_access(amqp)
ironic.assess_status()
@reactive.when('shared-db.connected')
def setup_database(database):
ironic.setup_database(database)
ironic.assess_status()
@adapters.config_property
def deployment_interface_ip(args):
return ch_ip.get_relation_ip("deployment")
@adapters.config_property
def internal_interface_ip(args):
return ch_ip.get_relation_ip("internal")

View File

@@ -0,0 +1,9 @@
TFTP_USERNAME="root"
TFTP_DIRECTORY="{{ options.tftpboot }}"
TFTP_ADDRESS=":69"
{% if options.max_tftp_block_size != 0 %}
TFTP_OPTIONS="-4 -v -v -v -v -v --map-file {{options.tftpboot}}/map-file --blocksize {{ options.max_tftp_block_size }}"
{% else %}
TFTP_OPTIONS="-4 -v -v -v -v -v --map-file {{options.tftpboot}}/map-file"
{% endif %}

View File

@@ -0,0 +1,27 @@
# An ironic-lib.filters to be used with rootwrap command.
# The following commands should be used in filters for disk manipulation.
# This file should be owned by (and only-writeable by) the root user.
# NOTE:
# if you update this file, you will also need to adjust the
# ironic-lib.filters from the ironic module.
[Filters]
# ironic_lib/disk_utils.py
blkid: CommandFilter, blkid, root
blockdev: CommandFilter, blockdev, root
hexdump: CommandFilter, hexdump, root
lsblk: CommandFilter, lsblk, root
qemu-img: CommandFilter, qemu-img, root
wipefs: CommandFilter, wipefs, root
sgdisk: CommandFilter, sgdisk, root
partprobe: CommandFilter, partprobe, root
# ironic_lib/utils.py
mkswap: CommandFilter, mkswap, root
mkfs: CommandFilter, mkfs, root
dd: CommandFilter, dd, root
# ironic_lib/disk_partitioner.py
fuser: CommandFilter, fuser, root
parted: CommandFilter, parted, root

View File

@@ -0,0 +1,10 @@
# ironic-rootwrap command filters for disk manipulation
# This file should be owned by (and only-writable by) the root user
[Filters]
# ironic/drivers/modules/deploy_utils.py
iscsiadm: CommandFilter, iscsiadm, root
# ironic/common/utils.py
mount: CommandFilter, mount, root
umount: CommandFilter, umount, root

View File

@@ -0,0 +1,40 @@
user {{options.ironic_user}};
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip off;
server {
listen {{options.ipxe_http_port}} default_server;
listen [::]:{{options.ipxe_http_port}} default_server;
root {{options.httpboot}};
index index.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
}

View File

@@ -0,0 +1,36 @@
{% if identity_credentials.auth_host -%}
{% if identity_credentials.api_version and identity_credentials.api_version == "3" %}
{% set auth_ver = "v3" %}
{% else %}
{% set auth_ver = "v2.0" %}
{% endif %}
[keystone_authtoken]
auth_version = {{auth_ver}}
www_authenticate_uri = {{ identity_credentials.auth_protocol }}://{{ identity_credentials.auth_host }}:{{ identity_credentials.credentials_port }}/{{auth_ver}}
auth_url = {{ identity_credentials.auth_protocol }}://{{ identity_credentials.auth_host }}:{{ identity_credentials.credentials_port }}
auth_type = password
{% if identity_credentials.credentials_project_domain_name -%}
project_domain_name = {{ identity_credentials.credentials_project_domain_name }}
user_domain_name = {{ identity_credentials.credentials_user_domain_name }}
{% else %}
project_domain_name = default
user_domain_name = default
{% endif -%}
username = {{ identity_credentials.credentials_username }}
password = {{ identity_credentials.credentials_password }}
project_name = {{identity_credentials.credentials_project}}
admin_user = {{ identity_credentials.credentials_username }}
admin_password = {{ identity_credentials.credentials_password }}
admin_tenant_name = {{identity_credentials.credentials_project}}
{% if identity_credentials.signing_dir -%}
signing_dir = {{ identity_credentials.signing_dir }}
{% endif -%}
{% if options.use_memcache == true -%}
memcached_servers = {{ options.memcache_url }}
{% endif -%}
{% endif -%}

View File

@@ -0,0 +1,14 @@
[deploy]
{% if options.use_ipxe -%}
# Ironic compute node's http root path. (string value)
http_root=/httpboot
# Ironic compute node's HTTP server URL (string value)
http_url=http://{{ options.deployment_interface_ip }}:{{ options.ipxe_http_port}}
{% endif -%}
{% if options.disable_secure_erase %}
enable_ata_secure_erase = false
shred_random_overwrite_iterations = 0
shred_final_overwrite_with_zeros = false
{% endif %}

View File

@@ -0,0 +1,30 @@
[pxe]
# Ironic compute node's tftp root path. (string value)
{% if not tftpboot -%}
{% set tftpboot = "/tftpboot" -%}
{% endif -%}
tftp_root={{tftpboot}}
# IP address of Ironic compute node's tftp server. (string
# value)
tftp_server = {{ options.deployment_interface_ip }}
pxe_append_params = nofb nomodeset vga=normal console=tty0 console=ttyS0,115200n8
{% if options.use_ipxe -%}
# Enable iPXE boot. (boolean value)
ipxe_enabled=True
# Neutron bootfile DHCP parameter. (string value)
pxe_bootfile_name=undionly.kpxe
# Bootfile DHCP parameter for UEFI boot mode. (string value)
uefi_pxe_bootfile_name=ipxe.efi
# Template file for PXE configuration. (string value)
pxe_config_template=$pybasedir/drivers/modules/ipxe_config.template
# Template file for PXE configuration for UEFI boot loader.
# (string value)
uefi_pxe_config_template=$pybasedir/drivers/modules/ipxe_config.template
{% endif -%}

View File

@@ -0,0 +1,31 @@
# Authentication type to load (string value)
auth_type = password
# Authentication URL (string value)
auth_url = {{ identity_credentials.auth_protocol }}://{{ identity_credentials.auth_host }}:{{ identity_credentials.credentials_port }}
# Username (string value)
username = {{ identity_credentials.credentials_username }}
# User's password (string value)
password = {{ identity_credentials.credentials_password }}
# Project name to scope to (string value)
project_name = {{identity_credentials.credentials_project}}
{% if identity_credentials.service_domain -%}
project_domain_name = {{ identity_credentials.credentials_project_domain_name }}
user_domain_name = {{ identity_credentials.credentials_user_domain_name }}
{% else -%}
project_domain_name = default
user_domain_name = default
{% endif -%}
{% if options.ca_cert_path -%}
# PEM encoded Certificate Authority to use when verifying
# HTTPs connections. (string value)
cafile = {{ options.ca_cert_path }}
{% endif -%}
region_name = {{ options.region }}

View File

@@ -0,0 +1,7 @@
set default=master
set timeout=5
set hidden_timeout_quiet=false
menuentry "master" {
configfile {{options.tftpboot}}/$net_default_ip.conf
}

View File

@@ -0,0 +1,4 @@
re ^({{options.tftpboot}}/) {{options.tftpboot}}/\2
re ^{{options.tftpboot}}/ {{options.tftpboot}}/
re ^(^/) {{options.tftpboot}}/\1
re ^([^/]) {{options.tftpboot}}/\1

View File

@@ -0,0 +1,56 @@
[DEFAULT]
debug = {{ options.debug }}
verbose = {{ options.verbose }}
auth_strategy=keystone
my_ip = {{ options.internal_interface_ip }}
enabled_deploy_interfaces = iscsi
enabled_hardware_types = ipmi,ilo,idrac,redfish,irmc
{% if options.use_ipxe -%}
enabled_boot_interfaces = pxe,ipxe,ilo-pxe,ilo-ipxe,irmc-pxe
{% else -%}
enabled_boot_interfaces = pxe,ilo-pxe,irmc-pxe
{% endif -%}
enabled_management_interfaces = ipmitool,redfish,ilo,irmc,idrac,noop
enabled_inspect_interfaces = idrac,ilo,irmc,redfish,no-inspect
enabled_network_interfaces = flat,neutron,noop
enabled_power_interfaces = ipmitool,redfish,ilo,irmc,idrac
enabled_storage_interfaces = cinder,noop
enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console
enabled_raid_interfaces = agent,idrac,irmc,no-raid
enabled_vendor_interfaces = ipmitool,idrac,ilo,no-vendor
default_deploy_interface = iscsi
default_network_interface = flat
transport_url = {{ amqp.transport_url }}
{% include "parts/keystone-authtoken" %}
[database]
{% include "parts/database" %}
[neutron]
{% include "parts/service-auth" %}
# {% if options.cleaning_network %}
# cleaning_network = {{ options.cleaning_network }}
# {% endif %}
# {% if options.provisioning_network %}
# provisioning_network = {{ options.provisioning_network }}
# {% endif %}
cleaning_network = 512147a6-37ed-4ab4-ac4c-c55bb845de8e
provisioning_network = 512147a6-37ed-4ab4-ac4c-c55bb845de8e
[glance]
{% include "parts/service-auth" %}
[cinder]
{% include "parts/service-auth" %}
[service_catalog]
{% include "parts/service-auth" %}
{% include "parts/section-pxe" %}
{% include "parts/section-deploy" %}

View File

@@ -0,0 +1,27 @@
# Configuration for ironic-rootwrap
# This file should be owned by (and only writable by) the root user
[DEFAULT]
# List of directories to load filter definitions from (separated by ',').
# These directories MUST all be only writable by root !
filters_path=/etc/ironic/rootwrap.d
# List of directories to search executables in, in case filters do not
# explicitly specify a full path (separated by ',')
# If not specified, defaults to system PATH environment variable.
# These directories MUST all be only writable by root !
exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin
# Enable logging to syslog
# Default value is False
use_syslog=False
# Which syslog facility to use.
# Valid values include auth, authpriv, syslog, user0, user1...
# Default value is 'syslog'
syslog_log_facility=syslog
# Which messages to log.
# INFO means log all usage
# ERROR means only log unsuccessful attempts
syslog_log_level=ERROR

5
tests/00-setup Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
sudo add-apt-repository ppa:juju/stable -y
sudo apt-get update
sudo apt-get install amulet python-requests -y

35
tests/10-deploy Executable file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/python3
import amulet
import requests
import unittest
class TestCharm(unittest.TestCase):
def setUp(self):
self.d = amulet.Deployment()
self.d.add('charm-ironic')
self.d.expose('charm-ironic')
self.d.setup(timeout=900)
self.d.sentry.wait()
self.unit = self.d.sentry['charm-ironic'][0]
def test_service(self):
# test we can access over http
page = requests.get('http://{}'.format(self.unit.info['public-address']))
self.assertEqual(page.status_code, 200)
# Now you can use self.d.sentry[SERVICE][UNIT] to address each of the units and perform
# more in-depth steps. Each self.d.sentry[SERVICE][UNIT] has the following methods:
# - .info - An array of the information of that unit from Juju
# - .file(PATH) - Get the details of a file on that unit
# - .file_contents(PATH) - Get plain text output of PATH file from that unit
# - .directory(PATH) - Get details of directory
# - .directory_contents(PATH) - List files and folders in PATH on that unit
# - .relation(relation, service:rel) - Get relation data from return service
if __name__ == '__main__':
unittest.main()