Pre-freeze 'make *-sync'
Change-Id: I7f029e37e4743587912c3264a38556caf8e00b3a
This commit is contained in:
parent
6afe8288a8
commit
f02caf0743
|
@ -13,13 +13,17 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import copy
|
||||||
|
import enum
|
||||||
import glob
|
import glob
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from subprocess import check_call, CalledProcessError
|
from subprocess import check_call, CalledProcessError
|
||||||
|
|
||||||
|
@ -50,7 +54,8 @@ from charmhelpers.core.hookenv import (
|
||||||
INFO,
|
INFO,
|
||||||
ERROR,
|
ERROR,
|
||||||
status_set,
|
status_set,
|
||||||
network_get_primary_address
|
network_get_primary_address,
|
||||||
|
WARNING,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.core.sysctl import create as sysctl_create
|
from charmhelpers.core.sysctl import create as sysctl_create
|
||||||
|
@ -110,6 +115,13 @@ from charmhelpers.contrib.openstack.utils import (
|
||||||
)
|
)
|
||||||
from charmhelpers.core.unitdata import kv
|
from charmhelpers.core.unitdata import kv
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sriov_netplan_shim import pci
|
||||||
|
except ImportError:
|
||||||
|
# The use of the function and contexts that require the pci module is
|
||||||
|
# optional.
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -263,6 +275,12 @@ class SharedDBContext(OSContextGenerator):
|
||||||
'database_password': rdata.get(password_setting),
|
'database_password': rdata.get(password_setting),
|
||||||
'database_type': 'mysql+pymysql'
|
'database_type': 'mysql+pymysql'
|
||||||
}
|
}
|
||||||
|
# Port is being introduced with LP Bug #1876188
|
||||||
|
# but it not currently required and may not be set in all
|
||||||
|
# cases, particularly in classic charms.
|
||||||
|
port = rdata.get('db_port')
|
||||||
|
if port:
|
||||||
|
ctxt['database_port'] = port
|
||||||
if CompareOpenStackReleases(rel) < 'queens':
|
if CompareOpenStackReleases(rel) < 'queens':
|
||||||
ctxt['database_type'] = 'mysql'
|
ctxt['database_type'] = 'mysql'
|
||||||
if self.context_complete(ctxt):
|
if self.context_complete(ctxt):
|
||||||
|
@ -2396,3 +2414,734 @@ class DHCPAgentContext(OSContextGenerator):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return _config
|
return _config
|
||||||
|
|
||||||
|
|
||||||
|
EntityMac = collections.namedtuple('EntityMac', ['entity', 'mac'])
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_pci_from_mapping_config(config_key):
|
||||||
|
"""Resolve local PCI devices from MAC addresses in mapping config.
|
||||||
|
|
||||||
|
Note that this function keeps record of mac->PCI address lookups
|
||||||
|
in the local unit db as the devices will disappaear from the system
|
||||||
|
once bound.
|
||||||
|
|
||||||
|
:param config_key: Configuration option key to parse data from
|
||||||
|
:type config_key: str
|
||||||
|
:returns: PCI device address to Tuple(entity, mac) map
|
||||||
|
:rtype: collections.OrderedDict[str,Tuple[str,str]]
|
||||||
|
"""
|
||||||
|
devices = pci.PCINetDevices()
|
||||||
|
resolved_devices = collections.OrderedDict()
|
||||||
|
db = kv()
|
||||||
|
# Note that ``parse_data_port_mappings`` returns Dict regardless of input
|
||||||
|
for mac, entity in parse_data_port_mappings(config(config_key)).items():
|
||||||
|
pcidev = devices.get_device_from_mac(mac)
|
||||||
|
if pcidev:
|
||||||
|
# NOTE: store mac->pci allocation as post binding
|
||||||
|
# it disappears from PCIDevices.
|
||||||
|
db.set(mac, pcidev.pci_address)
|
||||||
|
db.flush()
|
||||||
|
|
||||||
|
pci_address = db.get(mac)
|
||||||
|
if pci_address:
|
||||||
|
resolved_devices[pci_address] = EntityMac(entity, mac)
|
||||||
|
|
||||||
|
return resolved_devices
|
||||||
|
|
||||||
|
|
||||||
|
class DPDKDeviceContext(OSContextGenerator):
|
||||||
|
|
||||||
|
def __init__(self, driver_key=None, bridges_key=None, bonds_key=None):
|
||||||
|
"""Initialize DPDKDeviceContext.
|
||||||
|
|
||||||
|
:param driver_key: Key to use when retrieving driver config.
|
||||||
|
:type driver_key: str
|
||||||
|
:param bridges_key: Key to use when retrieving bridge config.
|
||||||
|
:type bridges_key: str
|
||||||
|
:param bonds_key: Key to use when retrieving bonds config.
|
||||||
|
:type bonds_key: str
|
||||||
|
"""
|
||||||
|
self.driver_key = driver_key or 'dpdk-driver'
|
||||||
|
self.bridges_key = bridges_key or 'data-port'
|
||||||
|
self.bonds_key = bonds_key or 'dpdk-bond-mappings'
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""Populate context.
|
||||||
|
|
||||||
|
:returns: context
|
||||||
|
:rtype: Dict[str,Union[str,collections.OrderedDict[str,str]]]
|
||||||
|
"""
|
||||||
|
driver = config(self.driver_key)
|
||||||
|
if driver is None:
|
||||||
|
return {}
|
||||||
|
# Resolve PCI devices for both directly used devices (_bridges)
|
||||||
|
# and devices for use in dpdk bonds (_bonds)
|
||||||
|
pci_devices = resolve_pci_from_mapping_config(self.bridges_key)
|
||||||
|
pci_devices.update(resolve_pci_from_mapping_config(self.bonds_key))
|
||||||
|
return {'devices': pci_devices,
|
||||||
|
'driver': driver}
|
||||||
|
|
||||||
|
|
||||||
|
class OVSDPDKDeviceContext(OSContextGenerator):
|
||||||
|
|
||||||
|
def __init__(self, bridges_key=None, bonds_key=None):
|
||||||
|
"""Initialize OVSDPDKDeviceContext.
|
||||||
|
|
||||||
|
:param bridges_key: Key to use when retrieving bridge config.
|
||||||
|
:type bridges_key: str
|
||||||
|
:param bonds_key: Key to use when retrieving bonds config.
|
||||||
|
:type bonds_key: str
|
||||||
|
"""
|
||||||
|
self.bridges_key = bridges_key or 'data-port'
|
||||||
|
self.bonds_key = bonds_key or 'dpdk-bond-mappings'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_cpu_list(cpulist):
|
||||||
|
"""Parses a linux cpulist for a numa node
|
||||||
|
|
||||||
|
:returns: list of cores
|
||||||
|
:rtype: List[int]
|
||||||
|
"""
|
||||||
|
cores = []
|
||||||
|
ranges = cpulist.split(',')
|
||||||
|
for cpu_range in ranges:
|
||||||
|
if "-" in cpu_range:
|
||||||
|
cpu_min_max = cpu_range.split('-')
|
||||||
|
cores += range(int(cpu_min_max[0]),
|
||||||
|
int(cpu_min_max[1]) + 1)
|
||||||
|
else:
|
||||||
|
cores.append(int(cpu_range))
|
||||||
|
return cores
|
||||||
|
|
||||||
|
def _numa_node_cores(self):
|
||||||
|
"""Get map of numa node -> cpu core
|
||||||
|
|
||||||
|
:returns: map of numa node -> cpu core
|
||||||
|
:rtype: Dict[str,List[int]]
|
||||||
|
"""
|
||||||
|
nodes = {}
|
||||||
|
node_regex = '/sys/devices/system/node/node*'
|
||||||
|
for node in glob.glob(node_regex):
|
||||||
|
index = node.lstrip('/sys/devices/system/node/node')
|
||||||
|
with open(os.path.join(node, 'cpulist')) as cpulist:
|
||||||
|
nodes[index] = self._parse_cpu_list(cpulist.read().strip())
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
def cpu_mask(self):
|
||||||
|
"""Get hex formatted CPU mask
|
||||||
|
|
||||||
|
The mask is based on using the first config:dpdk-socket-cores
|
||||||
|
cores of each NUMA node in the unit.
|
||||||
|
:returns: hex formatted CPU mask
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
num_cores = config('dpdk-socket-cores')
|
||||||
|
mask = 0
|
||||||
|
for cores in self._numa_node_cores().values():
|
||||||
|
for core in cores[:num_cores]:
|
||||||
|
mask = mask | 1 << core
|
||||||
|
return format(mask, '#04x')
|
||||||
|
|
||||||
|
def socket_memory(self):
|
||||||
|
"""Formatted list of socket memory configuration per NUMA node
|
||||||
|
|
||||||
|
:returns: socket memory configuration per NUMA node
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
sm_size = config('dpdk-socket-memory')
|
||||||
|
node_regex = '/sys/devices/system/node/node*'
|
||||||
|
mem_list = [str(sm_size) for _ in glob.glob(node_regex)]
|
||||||
|
if mem_list:
|
||||||
|
return ','.join(mem_list)
|
||||||
|
else:
|
||||||
|
return str(sm_size)
|
||||||
|
|
||||||
|
def devices(self):
|
||||||
|
"""List of PCI devices for use by DPDK
|
||||||
|
|
||||||
|
:returns: List of PCI devices for use by DPDK
|
||||||
|
:rtype: collections.OrderedDict[str,str]
|
||||||
|
"""
|
||||||
|
pci_devices = resolve_pci_from_mapping_config(self.bridges_key)
|
||||||
|
pci_devices.update(resolve_pci_from_mapping_config(self.bonds_key))
|
||||||
|
return pci_devices
|
||||||
|
|
||||||
|
def _formatted_whitelist(self, flag):
|
||||||
|
"""Flag formatted list of devices to whitelist
|
||||||
|
|
||||||
|
:param flag: flag format to use
|
||||||
|
:type flag: str
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
whitelist = []
|
||||||
|
for device in self.devices():
|
||||||
|
whitelist.append(flag.format(device=device))
|
||||||
|
return ' '.join(whitelist)
|
||||||
|
|
||||||
|
def device_whitelist(self):
|
||||||
|
"""Formatted list of devices to whitelist for dpdk
|
||||||
|
|
||||||
|
using the old style '-w' flag
|
||||||
|
|
||||||
|
:returns: devices to whitelist prefixed by '-w '
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return self._formatted_whitelist('-w {device}')
|
||||||
|
|
||||||
|
def pci_whitelist(self):
|
||||||
|
"""Formatted list of devices to whitelist for dpdk
|
||||||
|
|
||||||
|
using the new style '--pci-whitelist' flag
|
||||||
|
|
||||||
|
:returns: devices to whitelist prefixed by '--pci-whitelist '
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return self._formatted_whitelist('--pci-whitelist {device}')
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""Populate context.
|
||||||
|
|
||||||
|
:returns: context
|
||||||
|
:rtype: Dict[str,Union[bool,str]]
|
||||||
|
"""
|
||||||
|
ctxt = {}
|
||||||
|
whitelist = self.device_whitelist()
|
||||||
|
if whitelist:
|
||||||
|
ctxt['dpdk_enabled'] = config('enable-dpdk')
|
||||||
|
ctxt['device_whitelist'] = self.device_whitelist()
|
||||||
|
ctxt['socket_memory'] = self.socket_memory()
|
||||||
|
ctxt['cpu_mask'] = self.cpu_mask()
|
||||||
|
return ctxt
|
||||||
|
|
||||||
|
|
||||||
|
class BridgePortInterfaceMap(object):
|
||||||
|
"""Build a map of bridge ports and interaces from charm configuration.
|
||||||
|
|
||||||
|
NOTE: the handling of this detail in the charm is pre-deprecated.
|
||||||
|
|
||||||
|
The long term goal is for network connectivity detail to be modelled in
|
||||||
|
the server provisioning layer (such as MAAS) which in turn will provide
|
||||||
|
a Netplan YAML description that will be used to drive Open vSwitch.
|
||||||
|
|
||||||
|
Until we get to that reality the charm will need to configure this
|
||||||
|
detail based on application level configuration options.
|
||||||
|
|
||||||
|
There is a established way of mapping interfaces to ports and bridges
|
||||||
|
in the ``neutron-openvswitch`` and ``neutron-gateway`` charms and we
|
||||||
|
will carry that forward.
|
||||||
|
|
||||||
|
The relationship between bridge, port and interface(s).
|
||||||
|
+--------+
|
||||||
|
| bridge |
|
||||||
|
+--------+
|
||||||
|
|
|
||||||
|
+----------------+
|
||||||
|
| port aka. bond |
|
||||||
|
+----------------+
|
||||||
|
| |
|
||||||
|
+-+ +-+
|
||||||
|
|i| |i|
|
||||||
|
|n| |n|
|
||||||
|
|t| |t|
|
||||||
|
|0| |N|
|
||||||
|
+-+ +-+
|
||||||
|
"""
|
||||||
|
class interface_type(enum.Enum):
|
||||||
|
"""Supported interface types.
|
||||||
|
|
||||||
|
Supported interface types can be found in the ``iface_types`` column
|
||||||
|
in the ``Open_vSwitch`` table on a running system.
|
||||||
|
"""
|
||||||
|
dpdk = 'dpdk'
|
||||||
|
internal = 'internal'
|
||||||
|
system = 'system'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Return string representation of value.
|
||||||
|
|
||||||
|
:returns: string representation of value.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def __init__(self, bridges_key=None, bonds_key=None, enable_dpdk_key=None,
|
||||||
|
global_mtu=None):
|
||||||
|
"""Initialize map.
|
||||||
|
|
||||||
|
:param bridges_key: Name of bridge:interface/port map config key
|
||||||
|
(default: 'data-port')
|
||||||
|
:type bridges_key: Optional[str]
|
||||||
|
:param bonds_key: Name of port-name:interface map config key
|
||||||
|
(default: 'dpdk-bond-mappings')
|
||||||
|
:type bonds_key: Optional[str]
|
||||||
|
:param enable_dpdk_key: Name of DPDK toggle config key
|
||||||
|
(default: 'enable-dpdk')
|
||||||
|
:type enable_dpdk_key: Optional[str]
|
||||||
|
:param global_mtu: Set a MTU on all interfaces at map initialization.
|
||||||
|
|
||||||
|
The default is to have Open vSwitch get this from the underlying
|
||||||
|
interface as set up by bare metal provisioning.
|
||||||
|
|
||||||
|
Note that you can augment the MTU on an individual interface basis
|
||||||
|
like this:
|
||||||
|
|
||||||
|
ifdatamap = bpi.get_ifdatamap(bridge, port)
|
||||||
|
ifdatamap = {
|
||||||
|
port: {
|
||||||
|
**ifdata,
|
||||||
|
**{'mtu-request': my_individual_mtu_map[port]},
|
||||||
|
}
|
||||||
|
for port, ifdata in ifdatamap.items()
|
||||||
|
}
|
||||||
|
:type global_mtu: Optional[int]
|
||||||
|
"""
|
||||||
|
bridges_key = bridges_key or 'data-port'
|
||||||
|
bonds_key = bonds_key or 'dpdk-bond-mappings'
|
||||||
|
enable_dpdk_key = enable_dpdk_key or 'enable-dpdk'
|
||||||
|
self._map = collections.defaultdict(
|
||||||
|
lambda: collections.defaultdict(dict))
|
||||||
|
self._ifname_mac_map = collections.defaultdict(list)
|
||||||
|
self._mac_ifname_map = {}
|
||||||
|
self._mac_pci_address_map = {}
|
||||||
|
|
||||||
|
# First we iterate over the list of physical interfaces visible to the
|
||||||
|
# system and update interface name to mac and mac to interface name map
|
||||||
|
for ifname in list_nics():
|
||||||
|
if not is_phy_iface(ifname):
|
||||||
|
continue
|
||||||
|
mac = get_nic_hwaddr(ifname)
|
||||||
|
self._ifname_mac_map[ifname] = [mac]
|
||||||
|
self._mac_ifname_map[mac] = ifname
|
||||||
|
|
||||||
|
# In light of the pre-deprecation notice in the docstring of this
|
||||||
|
# class we will expose the ability to configure OVS bonds as a
|
||||||
|
# DPDK-only feature, but generally use the data structures internally.
|
||||||
|
if config(enable_dpdk_key):
|
||||||
|
# resolve PCI address of interfaces listed in the bridges and bonds
|
||||||
|
# charm configuration options. Note that for already bound
|
||||||
|
# interfaces the helper will retrieve MAC address from the unit
|
||||||
|
# KV store as the information is no longer available in sysfs.
|
||||||
|
_pci_bridge_mac = resolve_pci_from_mapping_config(
|
||||||
|
bridges_key)
|
||||||
|
_pci_bond_mac = resolve_pci_from_mapping_config(
|
||||||
|
bonds_key)
|
||||||
|
|
||||||
|
for pci_address, bridge_mac in _pci_bridge_mac.items():
|
||||||
|
if bridge_mac.mac in self._mac_ifname_map:
|
||||||
|
# if we already have the interface name in our map it is
|
||||||
|
# visible to the system and therefore not bound to DPDK
|
||||||
|
continue
|
||||||
|
ifname = 'dpdk-{}'.format(
|
||||||
|
hashlib.sha1(
|
||||||
|
pci_address.encode('UTF-8')).hexdigest()[:7])
|
||||||
|
self._ifname_mac_map[ifname] = [bridge_mac.mac]
|
||||||
|
self._mac_ifname_map[bridge_mac.mac] = ifname
|
||||||
|
self._mac_pci_address_map[bridge_mac.mac] = pci_address
|
||||||
|
|
||||||
|
for pci_address, bond_mac in _pci_bond_mac.items():
|
||||||
|
# for bonds we want to be able to get a list of macs from
|
||||||
|
# the bond name and also get at the interface name made up
|
||||||
|
# of the hash of the PCI address
|
||||||
|
ifname = 'dpdk-{}'.format(
|
||||||
|
hashlib.sha1(
|
||||||
|
pci_address.encode('UTF-8')).hexdigest()[:7])
|
||||||
|
self._ifname_mac_map[bond_mac.entity].append(bond_mac.mac)
|
||||||
|
self._mac_ifname_map[bond_mac.mac] = ifname
|
||||||
|
self._mac_pci_address_map[bond_mac.mac] = pci_address
|
||||||
|
|
||||||
|
config_bridges = config(bridges_key) or ''
|
||||||
|
for bridge, ifname_or_mac in (
|
||||||
|
pair.split(':', 1)
|
||||||
|
for pair in config_bridges.split()):
|
||||||
|
if ':' in ifname_or_mac:
|
||||||
|
try:
|
||||||
|
ifname = self.ifname_from_mac(ifname_or_mac)
|
||||||
|
except KeyError:
|
||||||
|
# The interface is destined for a different unit in the
|
||||||
|
# deployment.
|
||||||
|
continue
|
||||||
|
macs = [ifname_or_mac]
|
||||||
|
else:
|
||||||
|
ifname = ifname_or_mac
|
||||||
|
macs = self.macs_from_ifname(ifname_or_mac)
|
||||||
|
|
||||||
|
portname = ifname
|
||||||
|
for mac in macs:
|
||||||
|
try:
|
||||||
|
pci_address = self.pci_address_from_mac(mac)
|
||||||
|
iftype = self.interface_type.dpdk
|
||||||
|
ifname = self.ifname_from_mac(mac)
|
||||||
|
except KeyError:
|
||||||
|
pci_address = None
|
||||||
|
iftype = self.interface_type.system
|
||||||
|
|
||||||
|
self.add_interface(
|
||||||
|
bridge, portname, ifname, iftype, pci_address, global_mtu)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""Provide a Dict-like interface, get value of item.
|
||||||
|
|
||||||
|
:param key: Key to look up value from.
|
||||||
|
:type key: any
|
||||||
|
:returns: Value
|
||||||
|
:rtype: any
|
||||||
|
"""
|
||||||
|
return self._map.__getitem__(key)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""Provide a Dict-like interface, iterate over keys.
|
||||||
|
|
||||||
|
:returns: Iterator
|
||||||
|
:rtype: Iterator[any]
|
||||||
|
"""
|
||||||
|
return self._map.__iter__()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Provide a Dict-like interface, measure the length of internal map.
|
||||||
|
|
||||||
|
:returns: Length
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
return len(self._map)
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
"""Provide a Dict-like interface, iterate over items.
|
||||||
|
|
||||||
|
:returns: Key Value pairs
|
||||||
|
:rtype: Iterator[any, any]
|
||||||
|
"""
|
||||||
|
return self._map.items()
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
"""Provide a Dict-like interface, iterate over keys.
|
||||||
|
|
||||||
|
:returns: Iterator
|
||||||
|
:rtype: Iterator[any]
|
||||||
|
"""
|
||||||
|
return self._map.keys()
|
||||||
|
|
||||||
|
def ifname_from_mac(self, mac):
|
||||||
|
"""
|
||||||
|
:returns: Name of interface
|
||||||
|
:rtype: str
|
||||||
|
:raises: KeyError
|
||||||
|
"""
|
||||||
|
return (get_bond_master(self._mac_ifname_map[mac]) or
|
||||||
|
self._mac_ifname_map[mac])
|
||||||
|
|
||||||
|
def macs_from_ifname(self, ifname):
|
||||||
|
"""
|
||||||
|
:returns: List of hardware address (MAC) of interface
|
||||||
|
:rtype: List[str]
|
||||||
|
:raises: KeyError
|
||||||
|
"""
|
||||||
|
return self._ifname_mac_map[ifname]
|
||||||
|
|
||||||
|
def pci_address_from_mac(self, mac):
|
||||||
|
"""
|
||||||
|
:param mac: Hardware address (MAC) of interface
|
||||||
|
:type mac: str
|
||||||
|
:returns: PCI address of device associated with mac
|
||||||
|
:rtype: str
|
||||||
|
:raises: KeyError
|
||||||
|
"""
|
||||||
|
return self._mac_pci_address_map[mac]
|
||||||
|
|
||||||
|
def add_interface(self, bridge, port, ifname, iftype,
|
||||||
|
pci_address, mtu_request):
|
||||||
|
"""Add an interface to the map.
|
||||||
|
|
||||||
|
:param bridge: Name of bridge on which the bond will be added
|
||||||
|
:type bridge: str
|
||||||
|
:param port: Name of port which will represent the bond on bridge
|
||||||
|
:type port: str
|
||||||
|
:param ifname: Name of interface that will make up the bonded port
|
||||||
|
:type ifname: str
|
||||||
|
:param iftype: Type of interface
|
||||||
|
:type iftype: BridgeBondMap.interface_type
|
||||||
|
:param pci_address: PCI address of interface
|
||||||
|
:type pci_address: Optional[str]
|
||||||
|
:param mtu_request: MTU to request for interface
|
||||||
|
:type mtu_request: Optional[int]
|
||||||
|
"""
|
||||||
|
self._map[bridge][port][ifname] = {
|
||||||
|
'type': str(iftype),
|
||||||
|
}
|
||||||
|
if pci_address:
|
||||||
|
self._map[bridge][port][ifname].update({
|
||||||
|
'pci-address': pci_address,
|
||||||
|
})
|
||||||
|
if mtu_request is not None:
|
||||||
|
self._map[bridge][port][ifname].update({
|
||||||
|
'mtu-request': str(mtu_request)
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_ifdatamap(self, bridge, port):
|
||||||
|
"""Get structure suitable for charmhelpers.contrib.network.ovs helpers.
|
||||||
|
|
||||||
|
:param bridge: Name of bridge on which the port will be added
|
||||||
|
:type bridge: str
|
||||||
|
:param port: Name of port which will represent one or more interfaces
|
||||||
|
:type port: str
|
||||||
|
"""
|
||||||
|
for _bridge, _ports in self.items():
|
||||||
|
for _port, _interfaces in _ports.items():
|
||||||
|
if _bridge == bridge and _port == port:
|
||||||
|
ifdatamap = {}
|
||||||
|
for name, data in _interfaces.items():
|
||||||
|
ifdatamap.update({
|
||||||
|
name: {
|
||||||
|
'type': data['type'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if data.get('mtu-request') is not None:
|
||||||
|
ifdatamap[name].update({
|
||||||
|
'mtu_request': data['mtu-request'],
|
||||||
|
})
|
||||||
|
if data.get('pci-address'):
|
||||||
|
ifdatamap[name].update({
|
||||||
|
'options': {
|
||||||
|
'dpdk-devargs': data['pci-address'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ifdatamap
|
||||||
|
|
||||||
|
|
||||||
|
class BondConfig(object):
|
||||||
|
"""Container and helpers for bond configuration options.
|
||||||
|
|
||||||
|
Data is put into a dictionary and a convenient config get interface is
|
||||||
|
provided.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEFAULT_LACP_CONFIG = {
|
||||||
|
'mode': 'balance-tcp',
|
||||||
|
'lacp': 'active',
|
||||||
|
'lacp-time': 'fast'
|
||||||
|
}
|
||||||
|
ALL_BONDS = 'ALL_BONDS'
|
||||||
|
|
||||||
|
BOND_MODES = ['active-backup', 'balance-slb', 'balance-tcp']
|
||||||
|
BOND_LACP = ['active', 'passive', 'off']
|
||||||
|
BOND_LACP_TIME = ['fast', 'slow']
|
||||||
|
|
||||||
|
def __init__(self, config_key=None):
|
||||||
|
"""Parse specified configuration option.
|
||||||
|
|
||||||
|
:param config_key: Configuration key to retrieve data from
|
||||||
|
(default: ``dpdk-bond-config``)
|
||||||
|
:type config_key: Optional[str]
|
||||||
|
"""
|
||||||
|
self.config_key = config_key or 'dpdk-bond-config'
|
||||||
|
|
||||||
|
self.lacp_config = {
|
||||||
|
self.ALL_BONDS: copy.deepcopy(self.DEFAULT_LACP_CONFIG)
|
||||||
|
}
|
||||||
|
|
||||||
|
lacp_config = config(self.config_key)
|
||||||
|
if lacp_config:
|
||||||
|
lacp_config_map = lacp_config.split()
|
||||||
|
for entry in lacp_config_map:
|
||||||
|
bond, entry = entry.partition(':')[0:3:2]
|
||||||
|
if not bond:
|
||||||
|
bond = self.ALL_BONDS
|
||||||
|
|
||||||
|
mode, entry = entry.partition(':')[0:3:2]
|
||||||
|
if not mode:
|
||||||
|
mode = self.DEFAULT_LACP_CONFIG['mode']
|
||||||
|
assert mode in self.BOND_MODES, \
|
||||||
|
"Bond mode {} is invalid".format(mode)
|
||||||
|
|
||||||
|
lacp, entry = entry.partition(':')[0:3:2]
|
||||||
|
if not lacp:
|
||||||
|
lacp = self.DEFAULT_LACP_CONFIG['lacp']
|
||||||
|
assert lacp in self.BOND_LACP, \
|
||||||
|
"Bond lacp {} is invalid".format(lacp)
|
||||||
|
|
||||||
|
lacp_time, entry = entry.partition(':')[0:3:2]
|
||||||
|
if not lacp_time:
|
||||||
|
lacp_time = self.DEFAULT_LACP_CONFIG['lacp-time']
|
||||||
|
assert lacp_time in self.BOND_LACP_TIME, \
|
||||||
|
"Bond lacp-time {} is invalid".format(lacp_time)
|
||||||
|
|
||||||
|
self.lacp_config[bond] = {
|
||||||
|
'mode': mode,
|
||||||
|
'lacp': lacp,
|
||||||
|
'lacp-time': lacp_time
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_bond_config(self, bond):
|
||||||
|
"""Get the LACP configuration for a bond
|
||||||
|
|
||||||
|
:param bond: the bond name
|
||||||
|
:return: a dictionary with the configuration of the bond
|
||||||
|
:rtype: Dict[str,Dict[str,str]]
|
||||||
|
"""
|
||||||
|
return self.lacp_config.get(bond, self.lacp_config[self.ALL_BONDS])
|
||||||
|
|
||||||
|
def get_ovs_portdata(self, bond):
|
||||||
|
"""Get structure suitable for charmhelpers.contrib.network.ovs helpers.
|
||||||
|
|
||||||
|
:param bond: the bond name
|
||||||
|
:return: a dictionary with the configuration of the bond
|
||||||
|
:rtype: Dict[str,Union[str,Dict[str,str]]]
|
||||||
|
"""
|
||||||
|
bond_config = self.get_bond_config(bond)
|
||||||
|
return {
|
||||||
|
'bond_mode': bond_config['mode'],
|
||||||
|
'lacp': bond_config['lacp'],
|
||||||
|
'other_config': {
|
||||||
|
'lacp-time': bond_config['lacp-time'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SRIOVContext(OSContextGenerator):
|
||||||
|
"""Provide context for configuring SR-IOV devices."""
|
||||||
|
|
||||||
|
class sriov_config_mode(enum.Enum):
|
||||||
|
"""Mode in which SR-IOV is configured.
|
||||||
|
|
||||||
|
The configuration option identified by the ``numvfs_key`` parameter
|
||||||
|
is overloaded and defines in which mode the charm should interpret
|
||||||
|
the other SR-IOV-related configuration options.
|
||||||
|
"""
|
||||||
|
auto = 'auto'
|
||||||
|
blanket = 'blanket'
|
||||||
|
explicit = 'explicit'
|
||||||
|
|
||||||
|
def _determine_numvfs(self, device, sriov_numvfs):
|
||||||
|
"""Determine number of Virtual Functions (VFs) configured for device.
|
||||||
|
|
||||||
|
:param device: Object describing a PCI Network interface card (NIC)/
|
||||||
|
:type device: sriov_netplan_shim.pci.PCINetDevice
|
||||||
|
:param sriov_numvfs: Number of VFs requested for blanket configuration.
|
||||||
|
:type sriov_numvfs: int
|
||||||
|
:returns: Number of VFs to configure for device
|
||||||
|
:rtype: Optional[int]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_capped_numvfs(requested):
|
||||||
|
"""Get a number of VFs that does not exceed individual card limits.
|
||||||
|
|
||||||
|
Depending and make and model of NIC the number of VFs supported
|
||||||
|
vary. Requesting more VFs than a card support would be a fatal
|
||||||
|
error, cap the requested number at the total number of VFs each
|
||||||
|
individual card supports.
|
||||||
|
|
||||||
|
:param requested: Number of VFs requested
|
||||||
|
:type requested: int
|
||||||
|
:returns: Number of VFs allowed
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
actual = min(int(requested), int(device.sriov_totalvfs))
|
||||||
|
if actual < int(requested):
|
||||||
|
log('Requested VFs ({}) too high for device {}. Falling back '
|
||||||
|
'to value supprted by device: {}'
|
||||||
|
.format(requested, device.interface_name,
|
||||||
|
device.sriov_totalvfs),
|
||||||
|
level=WARNING)
|
||||||
|
return actual
|
||||||
|
|
||||||
|
if self._sriov_config_mode == self.sriov_config_mode.auto:
|
||||||
|
# auto-mode
|
||||||
|
#
|
||||||
|
# If device mapping configuration is present, return information
|
||||||
|
# on cards with mapping.
|
||||||
|
#
|
||||||
|
# If no device mapping configuration is present, return information
|
||||||
|
# for all cards.
|
||||||
|
#
|
||||||
|
# The maximum number of VFs supported by card will be used.
|
||||||
|
if (self._sriov_mapped_devices and
|
||||||
|
device.interface_name not in self._sriov_mapped_devices):
|
||||||
|
log('SR-IOV configured in auto mode: No device mapping for {}'
|
||||||
|
.format(device.interface_name),
|
||||||
|
level=DEBUG)
|
||||||
|
return
|
||||||
|
return _get_capped_numvfs(device.sriov_totalvfs)
|
||||||
|
elif self._sriov_config_mode == self.sriov_config_mode.blanket:
|
||||||
|
# blanket-mode
|
||||||
|
#
|
||||||
|
# User has specified a number of VFs that should apply to all
|
||||||
|
# cards with support for VFs.
|
||||||
|
return _get_capped_numvfs(sriov_numvfs)
|
||||||
|
elif self._sriov_config_mode == self.sriov_config_mode.explicit:
|
||||||
|
# explicit-mode
|
||||||
|
#
|
||||||
|
# User has given a list of interface names and associated number of
|
||||||
|
# VFs
|
||||||
|
if device.interface_name not in self._sriov_config_devices:
|
||||||
|
log('SR-IOV configured in explicit mode: No device:numvfs '
|
||||||
|
'pair for device {}, skipping.'
|
||||||
|
.format(device.interface_name),
|
||||||
|
level=DEBUG)
|
||||||
|
return
|
||||||
|
return _get_capped_numvfs(
|
||||||
|
self._sriov_config_devices[device.interface_name])
|
||||||
|
else:
|
||||||
|
raise RuntimeError('This should not be reached')
|
||||||
|
|
||||||
|
def __init__(self, numvfs_key=None, device_mappings_key=None):
|
||||||
|
"""Initialize map from PCI devices and configuration options.
|
||||||
|
|
||||||
|
:param numvfs_key: Config key for numvfs (default: 'sriov-numvfs')
|
||||||
|
:type numvfs_key: Optional[str]
|
||||||
|
:param device_mappings_key: Config key for device mappings
|
||||||
|
(default: 'sriov-device-mappings')
|
||||||
|
:type device_mappings_key: Optional[str]
|
||||||
|
:raises: RuntimeError
|
||||||
|
"""
|
||||||
|
numvfs_key = numvfs_key or 'sriov-numvfs'
|
||||||
|
device_mappings_key = device_mappings_key or 'sriov-device-mappings'
|
||||||
|
|
||||||
|
devices = pci.PCINetDevices()
|
||||||
|
charm_config = config()
|
||||||
|
sriov_numvfs = charm_config.get(numvfs_key) or ''
|
||||||
|
sriov_device_mappings = charm_config.get(device_mappings_key) or ''
|
||||||
|
|
||||||
|
# create list of devices from sriov_device_mappings config option
|
||||||
|
self._sriov_mapped_devices = [
|
||||||
|
pair.split(':', 1)[1]
|
||||||
|
for pair in sriov_device_mappings.split()
|
||||||
|
]
|
||||||
|
|
||||||
|
# create map of device:numvfs from sriov_numvfs config option
|
||||||
|
self._sriov_config_devices = {
|
||||||
|
ifname: numvfs for ifname, numvfs in (
|
||||||
|
pair.split(':', 1) for pair in sriov_numvfs.split()
|
||||||
|
if ':' in sriov_numvfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
# determine configuration mode from contents of sriov_numvfs
|
||||||
|
if sriov_numvfs == 'auto':
|
||||||
|
self._sriov_config_mode = self.sriov_config_mode.auto
|
||||||
|
elif sriov_numvfs.isdigit():
|
||||||
|
self._sriov_config_mode = self.sriov_config_mode.blanket
|
||||||
|
elif ':' in sriov_numvfs:
|
||||||
|
self._sriov_config_mode = self.sriov_config_mode.explicit
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Unable to determine mode of SR-IOV '
|
||||||
|
'configuration.')
|
||||||
|
|
||||||
|
self._map = {
|
||||||
|
device.interface_name: self._determine_numvfs(device, sriov_numvfs)
|
||||||
|
for device in devices.pci_devices
|
||||||
|
if device.sriov and
|
||||||
|
self._determine_numvfs(device, sriov_numvfs) is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""Provide SR-IOV context.
|
||||||
|
|
||||||
|
:returns: Map interface name: min(configured, max) virtual functions.
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
'eth0': 16,
|
||||||
|
'eth1': 32,
|
||||||
|
'eth2': 64,
|
||||||
|
}
|
||||||
|
:rtype: Dict[str,int]
|
||||||
|
"""
|
||||||
|
return self._map
|
||||||
|
|
|
@ -163,7 +163,16 @@ def retrieve_secret_id(url, token):
|
||||||
:returns: secret_id to use for Vault Access
|
:returns: secret_id to use for Vault Access
|
||||||
:rtype: str"""
|
:rtype: str"""
|
||||||
import hvac
|
import hvac
|
||||||
client = hvac.Client(url=url, token=token)
|
try:
|
||||||
|
# hvac 0.10.1 changed default adapter to JSONAdapter
|
||||||
|
client = hvac.Client(url=url, token=token, adapter=hvac.adapters.Request)
|
||||||
|
except AttributeError:
|
||||||
|
# hvac < 0.6.2 doesn't have adapter but uses the same response interface
|
||||||
|
client = hvac.Client(url=url, token=token)
|
||||||
|
else:
|
||||||
|
# hvac < 0.9.2 assumes adapter is an instance, so doesn't instantiate
|
||||||
|
if not isinstance(client.adapter, hvac.adapters.Request):
|
||||||
|
client.adapter = hvac.adapters.Request(base_uri=url, token=token)
|
||||||
response = client._post('/v1/sys/wrapping/unwrap')
|
response = client._post('/v1/sys/wrapping/unwrap')
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
|
@ -160,9 +160,10 @@ def handle_create_erasure_profile(request, service):
|
||||||
# "host" | "rack" or it defaults to "host" # Any valid Ceph bucket
|
# "host" | "rack" or it defaults to "host" # Any valid Ceph bucket
|
||||||
failure_domain = request.get('failure-domain')
|
failure_domain = request.get('failure-domain')
|
||||||
name = request.get('name')
|
name = request.get('name')
|
||||||
k = request.get('k')
|
# Binary Distribution Matrix (BDM) parameters
|
||||||
m = request.get('m')
|
bdm_k = request.get('k')
|
||||||
l = request.get('l')
|
bdm_m = request.get('m')
|
||||||
|
bdm_l = request.get('l')
|
||||||
|
|
||||||
if failure_domain not in CEPH_BUCKET_TYPES:
|
if failure_domain not in CEPH_BUCKET_TYPES:
|
||||||
msg = "failure-domain must be one of {}".format(CEPH_BUCKET_TYPES)
|
msg = "failure-domain must be one of {}".format(CEPH_BUCKET_TYPES)
|
||||||
|
@ -171,7 +172,8 @@ def handle_create_erasure_profile(request, service):
|
||||||
|
|
||||||
create_erasure_profile(service=service, erasure_plugin_name=erasure_type,
|
create_erasure_profile(service=service, erasure_plugin_name=erasure_type,
|
||||||
profile_name=name, failure_domain=failure_domain,
|
profile_name=name, failure_domain=failure_domain,
|
||||||
data_chunks=k, coding_chunks=m, locality=l)
|
data_chunks=bdm_k, coding_chunks=bdm_m,
|
||||||
|
locality=bdm_l)
|
||||||
|
|
||||||
|
|
||||||
def handle_add_permissions_to_key(request, service):
|
def handle_add_permissions_to_key(request, service):
|
||||||
|
@ -556,7 +558,7 @@ def handle_set_pool_value(request, service):
|
||||||
|
|
||||||
# Get the validation method
|
# Get the validation method
|
||||||
validator_params = POOL_KEYS[params['key']]
|
validator_params = POOL_KEYS[params['key']]
|
||||||
if len(validator_params) is 1:
|
if len(validator_params) == 1:
|
||||||
# Validate that what the user passed is actually legal per Ceph's rules
|
# Validate that what the user passed is actually legal per Ceph's rules
|
||||||
validator(params['value'], validator_params[0])
|
validator(params['value'], validator_params[0])
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -25,6 +25,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from charmhelpers.core import hookenv
|
from charmhelpers.core import hookenv
|
||||||
|
@ -175,12 +176,16 @@ def unmounted_disks():
|
||||||
context = pyudev.Context()
|
context = pyudev.Context()
|
||||||
for device in context.list_devices(DEVTYPE='disk'):
|
for device in context.list_devices(DEVTYPE='disk'):
|
||||||
if device['SUBSYSTEM'] == 'block':
|
if device['SUBSYSTEM'] == 'block':
|
||||||
|
if device.device_node is None:
|
||||||
|
continue
|
||||||
|
|
||||||
matched = False
|
matched = False
|
||||||
for block_type in [u'dm-', u'loop', u'ram', u'nbd']:
|
for block_type in [u'dm-', u'loop', u'ram', u'nbd']:
|
||||||
if block_type in device.device_node:
|
if block_type in device.device_node:
|
||||||
matched = True
|
matched = True
|
||||||
if matched:
|
if matched:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
disks.append(device.device_node)
|
disks.append(device.device_node)
|
||||||
log("Found disks: {}".format(disks))
|
log("Found disks: {}".format(disks))
|
||||||
return [disk for disk in disks if not is_device_mounted(disk)]
|
return [disk for disk in disks if not is_device_mounted(disk)]
|
||||||
|
@ -637,7 +642,7 @@ def _get_osd_num_from_dirname(dirname):
|
||||||
:raises ValueError: if the osd number cannot be parsed from the provided
|
:raises ValueError: if the osd number cannot be parsed from the provided
|
||||||
directory name.
|
directory name.
|
||||||
"""
|
"""
|
||||||
match = re.search('ceph-(?P<osd_id>\d+)', dirname)
|
match = re.search(r'ceph-(?P<osd_id>\d+)', dirname)
|
||||||
if not match:
|
if not match:
|
||||||
raise ValueError("dirname not in correct format: {}".format(dirname))
|
raise ValueError("dirname not in correct format: {}".format(dirname))
|
||||||
|
|
||||||
|
@ -706,7 +711,7 @@ def get_version():
|
||||||
package = "ceph"
|
package = "ceph"
|
||||||
try:
|
try:
|
||||||
pkg = cache[package]
|
pkg = cache[package]
|
||||||
except:
|
except KeyError:
|
||||||
# the package is unknown to the current apt cache.
|
# the package is unknown to the current apt cache.
|
||||||
e = 'Could not determine version of package with no installation ' \
|
e = 'Could not determine version of package with no installation ' \
|
||||||
'candidate: %s' % package
|
'candidate: %s' % package
|
||||||
|
@ -721,7 +726,7 @@ def get_version():
|
||||||
|
|
||||||
# x.y match only for 20XX.X
|
# x.y match only for 20XX.X
|
||||||
# and ignore patch level for other packages
|
# and ignore patch level for other packages
|
||||||
match = re.match('^(\d+)\.(\d+)', vers)
|
match = re.match(r'^(\d+)\.(\d+)', vers)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
vers = match.group(0)
|
vers = match.group(0)
|
||||||
|
@ -956,11 +961,11 @@ def start_osds(devices):
|
||||||
rescan_osd_devices()
|
rescan_osd_devices()
|
||||||
if (cmp_pkgrevno('ceph', '0.56.6') >= 0 and
|
if (cmp_pkgrevno('ceph', '0.56.6') >= 0 and
|
||||||
cmp_pkgrevno('ceph', '14.2.0') < 0):
|
cmp_pkgrevno('ceph', '14.2.0') < 0):
|
||||||
# Use ceph-disk activate for directory based OSD's
|
# Use ceph-disk activate for directory based OSD's
|
||||||
for dev_or_path in devices:
|
for dev_or_path in devices:
|
||||||
if os.path.exists(dev_or_path) and os.path.isdir(dev_or_path):
|
if os.path.exists(dev_or_path) and os.path.isdir(dev_or_path):
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
['ceph-disk', 'activate', dev_or_path])
|
['ceph-disk', 'activate', dev_or_path])
|
||||||
|
|
||||||
|
|
||||||
def udevadm_settle():
|
def udevadm_settle():
|
||||||
|
@ -978,6 +983,7 @@ def rescan_osd_devices():
|
||||||
|
|
||||||
udevadm_settle()
|
udevadm_settle()
|
||||||
|
|
||||||
|
|
||||||
_client_admin_keyring = '/etc/ceph/ceph.client.admin.keyring'
|
_client_admin_keyring = '/etc/ceph/ceph.client.admin.keyring'
|
||||||
|
|
||||||
|
|
||||||
|
@ -1002,6 +1008,7 @@ def generate_monitor_secret():
|
||||||
|
|
||||||
return "{}==".format(res.split('=')[1].strip())
|
return "{}==".format(res.split('=')[1].strip())
|
||||||
|
|
||||||
|
|
||||||
# OSD caps taken from ceph-create-keys
|
# OSD caps taken from ceph-create-keys
|
||||||
_osd_bootstrap_caps = {
|
_osd_bootstrap_caps = {
|
||||||
'mon': [
|
'mon': [
|
||||||
|
@ -1039,7 +1046,7 @@ def get_osd_bootstrap_key():
|
||||||
# Attempt to get/create a key using the OSD bootstrap profile first
|
# Attempt to get/create a key using the OSD bootstrap profile first
|
||||||
key = get_named_key('bootstrap-osd',
|
key = get_named_key('bootstrap-osd',
|
||||||
_osd_bootstrap_caps_profile)
|
_osd_bootstrap_caps_profile)
|
||||||
except:
|
except Exception:
|
||||||
# If that fails try with the older style permissions
|
# If that fails try with the older style permissions
|
||||||
key = get_named_key('bootstrap-osd',
|
key = get_named_key('bootstrap-osd',
|
||||||
_osd_bootstrap_caps)
|
_osd_bootstrap_caps)
|
||||||
|
@ -1063,6 +1070,7 @@ def import_radosgw_key(key):
|
||||||
]
|
]
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
# OSD caps taken from ceph-create-keys
|
# OSD caps taken from ceph-create-keys
|
||||||
_radosgw_caps = {
|
_radosgw_caps = {
|
||||||
'mon': ['allow rw'],
|
'mon': ['allow rw'],
|
||||||
|
@ -1300,7 +1308,7 @@ def bootstrap_monitor_cluster(secret):
|
||||||
path,
|
path,
|
||||||
done,
|
done,
|
||||||
init_marker)
|
init_marker)
|
||||||
except:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
os.unlink(keyring)
|
os.unlink(keyring)
|
||||||
|
@ -2417,10 +2425,11 @@ def upgrade_osd(new_version):
|
||||||
# way to update the code on the node.
|
# way to update the code on the node.
|
||||||
if not dirs_need_ownership_update('osd'):
|
if not dirs_need_ownership_update('osd'):
|
||||||
log('Restarting all OSDs to load new binaries', DEBUG)
|
log('Restarting all OSDs to load new binaries', DEBUG)
|
||||||
if systemd():
|
with maintain_all_osd_states():
|
||||||
service_restart('ceph-osd.target')
|
if systemd():
|
||||||
else:
|
service_restart('ceph-osd.target')
|
||||||
service_restart('ceph-osd-all')
|
else:
|
||||||
|
service_restart('ceph-osd-all')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Need to change the ownership of all directories which are not OSD
|
# Need to change the ownership of all directories which are not OSD
|
||||||
|
@ -2465,11 +2474,12 @@ def _upgrade_single_osd(osd_num, osd_dir):
|
||||||
:raises IOError: if an error occurs reading/writing to a file as part
|
:raises IOError: if an error occurs reading/writing to a file as part
|
||||||
of the upgrade process
|
of the upgrade process
|
||||||
"""
|
"""
|
||||||
stop_osd(osd_num)
|
with maintain_osd_state(osd_num):
|
||||||
disable_osd(osd_num)
|
stop_osd(osd_num)
|
||||||
update_owner(osd_dir)
|
disable_osd(osd_num)
|
||||||
enable_osd(osd_num)
|
update_owner(osd_dir)
|
||||||
start_osd(osd_num)
|
enable_osd(osd_num)
|
||||||
|
start_osd(osd_num)
|
||||||
|
|
||||||
|
|
||||||
def stop_osd(osd_num):
|
def stop_osd(osd_num):
|
||||||
|
@ -2596,6 +2606,98 @@ def update_owner(path, recurse_dirs=True):
|
||||||
secs=elapsed_time.total_seconds(), path=path), DEBUG)
|
secs=elapsed_time.total_seconds(), path=path), DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
def get_osd_state(osd_num, osd_goal_state=None):
|
||||||
|
"""Get OSD state or loop until OSD state matches OSD goal state.
|
||||||
|
|
||||||
|
If osd_goal_state is None, just return the current OSD state.
|
||||||
|
If osd_goal_state is not None, loop until the current OSD state matches
|
||||||
|
the OSD goal state.
|
||||||
|
|
||||||
|
:param osd_num: the osd id to get state for
|
||||||
|
:param osd_goal_state: (Optional) string indicating state to wait for
|
||||||
|
Defaults to None
|
||||||
|
:returns: Returns a str, the OSD state.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
asok = "/var/run/ceph/ceph-osd.{}.asok".format(osd_num)
|
||||||
|
cmd = [
|
||||||
|
'ceph',
|
||||||
|
'daemon',
|
||||||
|
asok,
|
||||||
|
'status'
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
result = json.loads(str(subprocess
|
||||||
|
.check_output(cmd)
|
||||||
|
.decode('UTF-8')))
|
||||||
|
except (subprocess.CalledProcessError, ValueError) as e:
|
||||||
|
log("{}".format(e), level=DEBUG)
|
||||||
|
continue
|
||||||
|
osd_state = result['state']
|
||||||
|
log("OSD {} state: {}, goal state: {}".format(
|
||||||
|
osd_num, osd_state, osd_goal_state), level=DEBUG)
|
||||||
|
if not osd_goal_state:
|
||||||
|
return osd_state
|
||||||
|
if osd_state == osd_goal_state:
|
||||||
|
return osd_state
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_osd_states(osd_goal_states=None):
|
||||||
|
"""Get all OSD states or loop until all OSD states match OSD goal states.
|
||||||
|
|
||||||
|
If osd_goal_states is None, just return a dictionary of current OSD states.
|
||||||
|
If osd_goal_states is not None, loop until the current OSD states match
|
||||||
|
the OSD goal states.
|
||||||
|
|
||||||
|
:param osd_goal_states: (Optional) dict indicating states to wait for
|
||||||
|
Defaults to None
|
||||||
|
:returns: Returns a dictionary of current OSD states.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
osd_states = {}
|
||||||
|
for osd_num in get_local_osd_ids():
|
||||||
|
if not osd_goal_states:
|
||||||
|
osd_states[osd_num] = get_osd_state(osd_num)
|
||||||
|
else:
|
||||||
|
osd_states[osd_num] = get_osd_state(
|
||||||
|
osd_num,
|
||||||
|
osd_goal_state=osd_goal_states[osd_num])
|
||||||
|
return osd_states
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def maintain_osd_state(osd_num):
|
||||||
|
"""Ensure the state of an OSD is maintained.
|
||||||
|
|
||||||
|
Ensures the state of an OSD is the same at the end of a block nested
|
||||||
|
in a with statement as it was at the beginning of the block.
|
||||||
|
|
||||||
|
:param osd_num: the osd id to maintain state for
|
||||||
|
"""
|
||||||
|
osd_state = get_osd_state(osd_num)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
get_osd_state(osd_num, osd_goal_state=osd_state)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def maintain_all_osd_states():
|
||||||
|
"""Ensure all local OSD states are maintained.
|
||||||
|
|
||||||
|
Ensures the states of all local OSDs are the same at the end of a
|
||||||
|
block nested in a with statement as they were at the beginning of
|
||||||
|
the block.
|
||||||
|
"""
|
||||||
|
osd_states = get_all_osd_states()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
get_all_osd_states(osd_goal_states=osd_states)
|
||||||
|
|
||||||
|
|
||||||
def list_pools(client='admin'):
|
def list_pools(client='admin'):
|
||||||
"""This will list the current pools that Ceph has
|
"""This will list the current pools that Ceph has
|
||||||
|
|
||||||
|
@ -2790,6 +2892,7 @@ def dirs_need_ownership_update(service):
|
||||||
# All child directories had the expected ownership
|
# All child directories had the expected ownership
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# A dict of valid ceph upgrade paths. Mapping is old -> new
|
# A dict of valid ceph upgrade paths. Mapping is old -> new
|
||||||
UPGRADE_PATHS = collections.OrderedDict([
|
UPGRADE_PATHS = collections.OrderedDict([
|
||||||
('firefly', 'hammer'),
|
('firefly', 'hammer'),
|
||||||
|
@ -2797,6 +2900,7 @@ UPGRADE_PATHS = collections.OrderedDict([
|
||||||
('jewel', 'luminous'),
|
('jewel', 'luminous'),
|
||||||
('luminous', 'mimic'),
|
('luminous', 'mimic'),
|
||||||
('mimic', 'nautilus'),
|
('mimic', 'nautilus'),
|
||||||
|
('nautilus', 'octopus'),
|
||||||
])
|
])
|
||||||
|
|
||||||
# Map UCA codenames to ceph codenames
|
# Map UCA codenames to ceph codenames
|
||||||
|
@ -2813,6 +2917,7 @@ UCA_CODENAME_MAP = {
|
||||||
'rocky': 'mimic',
|
'rocky': 'mimic',
|
||||||
'stein': 'mimic',
|
'stein': 'mimic',
|
||||||
'train': 'nautilus',
|
'train': 'nautilus',
|
||||||
|
'ussuri': 'octopus',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2965,3 +3070,57 @@ def osd_noout(enable):
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
log(e)
|
log(e)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class OSDConfigSetError(Exception):
|
||||||
|
"""Error occured applying OSD settings."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def apply_osd_settings(settings):
|
||||||
|
"""Applies the provided osd settings
|
||||||
|
|
||||||
|
Apply the provided settings to all local OSD unless settings are already
|
||||||
|
present. Settings stop being applied on encountering an error.
|
||||||
|
|
||||||
|
:param settings: dict. Dictionary of settings to apply.
|
||||||
|
:returns: bool. True if commands ran succesfully.
|
||||||
|
:raises: OSDConfigSetError
|
||||||
|
"""
|
||||||
|
current_settings = {}
|
||||||
|
base_cmd = 'ceph daemon osd.{osd_id} config --format=json'
|
||||||
|
get_cmd = base_cmd + ' get {key}'
|
||||||
|
set_cmd = base_cmd + ' set {key} {value}'
|
||||||
|
|
||||||
|
def _get_cli_key(key):
|
||||||
|
return(key.replace(' ', '_'))
|
||||||
|
# Retrieve the current values to check keys are correct and to make this a
|
||||||
|
# noop if setting are already applied.
|
||||||
|
for osd_id in get_local_osd_ids():
|
||||||
|
for key, value in sorted(settings.items()):
|
||||||
|
cli_key = _get_cli_key(key)
|
||||||
|
cmd = get_cmd.format(osd_id=osd_id, key=cli_key)
|
||||||
|
out = json.loads(
|
||||||
|
subprocess.check_output(cmd.split()).decode('UTF-8'))
|
||||||
|
if 'error' in out:
|
||||||
|
log("Error retrieving osd setting: {}".format(out['error']),
|
||||||
|
level=ERROR)
|
||||||
|
return False
|
||||||
|
current_settings[key] = out[cli_key]
|
||||||
|
settings_diff = {
|
||||||
|
k: v
|
||||||
|
for k, v in settings.items()
|
||||||
|
if str(v) != str(current_settings[k])}
|
||||||
|
for key, value in sorted(settings_diff.items()):
|
||||||
|
log("Setting {} to {}".format(key, value), level=DEBUG)
|
||||||
|
cmd = set_cmd.format(
|
||||||
|
osd_id=osd_id,
|
||||||
|
key=_get_cli_key(key),
|
||||||
|
value=value)
|
||||||
|
out = json.loads(
|
||||||
|
subprocess.check_output(cmd.split()).decode('UTF-8'))
|
||||||
|
if 'error' in out:
|
||||||
|
log("Error applying osd setting: {}".format(out['error']),
|
||||||
|
level=ERROR)
|
||||||
|
raise OSDConfigSetError
|
||||||
|
return True
|
||||||
|
|
Loading…
Reference in New Issue