Initial commit
This commit is contained in:
80
config.yaml
Normal file
80
config.yaml
Normal 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
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
10
layer.yaml
Normal 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
0
lib/__init__.py
Normal file
0
lib/charm/__init__.py
Normal file
0
lib/charm/__init__.py
Normal file
0
lib/charm/openstack/__init__.py
Normal file
0
lib/charm/openstack/__init__.py
Normal file
0
lib/charm/openstack/ironic/__init__.py
Normal file
0
lib/charm/openstack/ironic/__init__.py
Normal file
178
lib/charm/openstack/ironic/controller_utils.py
Normal file
178
lib/charm/openstack/ironic/controller_utils.py
Normal 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)
|
||||
167
lib/charm/openstack/ironic/ironic.py
Normal file
167
lib/charm/openstack/ironic/ironic.py
Normal 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
30
metadata.yaml
Normal 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
0
reactive/__init__.py
Normal file
58
reactive/ironic_handlers.py
Normal file
58
reactive/ironic_handlers.py
Normal 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")
|
||||
9
templates/etc_default_tftpd-hpa
Normal file
9
templates/etc_default_tftpd-hpa
Normal 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 %}
|
||||
27
templates/etc_ironic_rootwrap.d_ironic-lib.filters
Normal file
27
templates/etc_ironic_rootwrap.d_ironic-lib.filters
Normal 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
|
||||
10
templates/etc_ironic_rootwrap.d_ironic-utils.filters
Normal file
10
templates/etc_ironic_rootwrap.d_ironic-utils.filters
Normal 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
|
||||
40
templates/etc_nginx_nginx.conf
Normal file
40
templates/etc_nginx_nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
36
templates/parts/keystone-authtoken
Normal file
36
templates/parts/keystone-authtoken
Normal 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 -%}
|
||||
14
templates/parts/section-deploy
Normal file
14
templates/parts/section-deploy
Normal 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 %}
|
||||
30
templates/parts/section-pxe
Normal file
30
templates/parts/section-pxe
Normal 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 -%}
|
||||
31
templates/parts/service-auth
Normal file
31
templates/parts/service-auth
Normal 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 }}
|
||||
7
templates/tftpboot_grub_grub.cfg
Normal file
7
templates/tftpboot_grub_grub.cfg
Normal 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
|
||||
}
|
||||
4
templates/tftpboot_map-file
Normal file
4
templates/tftpboot_map-file
Normal file
@@ -0,0 +1,4 @@
|
||||
re ^({{options.tftpboot}}/) {{options.tftpboot}}/\2
|
||||
re ^{{options.tftpboot}}/ {{options.tftpboot}}/
|
||||
re ^(^/) {{options.tftpboot}}/\1
|
||||
re ^([^/]) {{options.tftpboot}}/\1
|
||||
56
templates/train/ironic.conf
Normal file
56
templates/train/ironic.conf
Normal 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" %}
|
||||
27
templates/train/rootwrap.conf
Normal file
27
templates/train/rootwrap.conf
Normal 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
5
tests/00-setup
Executable 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
35
tests/10-deploy
Executable 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()
|
||||
Reference in New Issue
Block a user