Pre-freeze 'make sync'
Change-Id: I0e06a7ec518eb44f1576089beadd95865bdbad04
This commit is contained in:
parent
810ee1b37d
commit
ff3ff8c5d1
|
@ -185,7 +185,7 @@ def del_bridge(name):
|
|||
|
||||
|
||||
def add_bridge_port(name, port, promisc=False, ifdata=None, exclusive=False,
|
||||
linkup=True):
|
||||
linkup=True, portdata=None):
|
||||
"""Add port to bridge and optionally set/update interface data for it
|
||||
|
||||
:param name: Name of bridge to attach port to
|
||||
|
@ -193,7 +193,8 @@ def add_bridge_port(name, port, promisc=False, ifdata=None, exclusive=False,
|
|||
:param port: Name of port as represented in netdev
|
||||
:type port: str
|
||||
:param promisc: Whether to set promiscuous mode on interface
|
||||
:type promisc: bool
|
||||
True=on, False=off, None leave untouched
|
||||
:type promisc: Optional[bool]
|
||||
:param ifdata: Additional data to attach to interface
|
||||
The keys in the ifdata dictionary map directly to column names in the
|
||||
OpenvSwitch Interface table as defined in DB-SCHEMA [0] referenced in
|
||||
|
@ -219,15 +220,18 @@ def add_bridge_port(name, port, promisc=False, ifdata=None, exclusive=False,
|
|||
:type exclusive: bool
|
||||
:param linkup: Bring link up
|
||||
:type linkup: bool
|
||||
:param portdata: Additional data to attach to port. Similar to ifdata.
|
||||
:type portdata: Optional[Dict[str,Union[str,Dict[str,str]]]]
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
cmd = ['ovs-vsctl', '--']
|
||||
if not exclusive:
|
||||
cmd.append('--may-exist')
|
||||
cmd.extend(('add-port', name, port))
|
||||
if ifdata:
|
||||
for setcmd in _dict_to_vsctl_set(ifdata, 'Interface', port):
|
||||
cmd.extend(setcmd)
|
||||
for ovs_table, data in (('Interface', ifdata), ('Port', portdata)):
|
||||
if data:
|
||||
for setcmd in _dict_to_vsctl_set(data, ovs_table, port):
|
||||
cmd.extend(setcmd)
|
||||
|
||||
log('Adding port {} to bridge {}'.format(port, name))
|
||||
subprocess.check_call(cmd)
|
||||
|
@ -238,7 +242,7 @@ def add_bridge_port(name, port, promisc=False, ifdata=None, exclusive=False,
|
|||
subprocess.check_call(["ip", "link", "set", port, "up"])
|
||||
if promisc:
|
||||
subprocess.check_call(["ip", "link", "set", port, "promisc", "on"])
|
||||
else:
|
||||
elif promisc is False:
|
||||
subprocess.check_call(["ip", "link", "set", port, "promisc", "off"])
|
||||
|
||||
|
||||
|
@ -258,6 +262,59 @@ def del_bridge_port(name, port):
|
|||
subprocess.check_call(["ip", "link", "set", port, "promisc", "off"])
|
||||
|
||||
|
||||
def add_bridge_bond(bridge, port, interfaces, portdata=None, ifdatamap=None,
|
||||
exclusive=False):
|
||||
"""Add bonded port in bridge from interfaces.
|
||||
|
||||
:param bridge: Name of bridge to add bonded port to
|
||||
:type bridge: str
|
||||
:param port: Name of created port
|
||||
:type port: str
|
||||
:param interfaces: Underlying interfaces that make up the bonded port
|
||||
:type interfaces: Iterator[str]
|
||||
:param portdata: Additional data to attach to the created bond port
|
||||
See _dict_to_vsctl_set() for detailed description.
|
||||
Example:
|
||||
{
|
||||
'bond-mode': 'balance-tcp',
|
||||
'lacp': 'active',
|
||||
'other-config': {
|
||||
'lacp-time': 'fast',
|
||||
},
|
||||
}
|
||||
:type portdata: Optional[Dict[str,Union[str,Dict[str,str]]]]
|
||||
:param ifdatamap: Map of data to attach to created bond interfaces
|
||||
See _dict_to_vsctl_set() for detailed description.
|
||||
Example:
|
||||
{
|
||||
'eth0': {
|
||||
'type': 'dpdk',
|
||||
'mtu-request': '9000',
|
||||
'options': {
|
||||
'dpdk-devargs': '0000:01:00.0',
|
||||
},
|
||||
},
|
||||
}
|
||||
:type ifdatamap: Optional[Dict[str,Dict[str,Union[str,Dict[str,str]]]]]
|
||||
:param exclusive: If True, raise exception if port exists
|
||||
:type exclusive: bool
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
cmd = ['ovs-vsctl', '--']
|
||||
if not exclusive:
|
||||
cmd.append('--may-exist')
|
||||
cmd.extend(('add-bond', bridge, port))
|
||||
cmd.extend(interfaces)
|
||||
if portdata:
|
||||
for setcmd in _dict_to_vsctl_set(portdata, 'port', port):
|
||||
cmd.extend(setcmd)
|
||||
if ifdatamap:
|
||||
for ifname, ifdata in ifdatamap.items():
|
||||
for setcmd in _dict_to_vsctl_set(ifdata, 'Interface', ifname):
|
||||
cmd.extend(setcmd)
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def add_ovsbridge_linuxbridge(name, bridge, ifdata=None):
|
||||
"""Add linux bridge to the named openvswitch bridge
|
||||
|
||||
|
|
|
@ -28,74 +28,179 @@ class SimpleOVSDB(object):
|
|||
appears to be a bit too involved for our simple use case.
|
||||
|
||||
Examples:
|
||||
chassis = SimpleOVSDB('ovn-sbctl', 'chassis')
|
||||
for chs in chassis:
|
||||
sbdb = SimpleOVSDB('ovn-sbctl')
|
||||
for chs in sbdb.chassis:
|
||||
print(chs)
|
||||
|
||||
bridges = SimpleOVSDB('ovs-vsctl', 'bridge')
|
||||
for br in bridges:
|
||||
ovsdb = SimpleOVSDB('ovs-vsctl')
|
||||
for br in ovsdb.bridge:
|
||||
if br['name'] == 'br-test':
|
||||
bridges.set(br['uuid'], 'external_ids:charm', 'managed')
|
||||
ovsdb.bridge.set(br['uuid'], 'external_ids:charm', 'managed')
|
||||
"""
|
||||
|
||||
def __init__(self, tool, table):
|
||||
"""SimpleOVSDB constructor
|
||||
# For validation we keep a complete map of currently known good tool and
|
||||
# table combinations. This requires maintenance down the line whenever
|
||||
# upstream adds things that downstream wants, and the cost of maintaining
|
||||
# that will most likely be lower then the cost of finding the needle in
|
||||
# the haystack whenever downstream code misspells something.
|
||||
_tool_table_map = {
|
||||
'ovs-vsctl': (
|
||||
'autoattach',
|
||||
'bridge',
|
||||
'ct_timeout_policy',
|
||||
'ct_zone',
|
||||
'controller',
|
||||
'datapath',
|
||||
'flow_sample_collector_set',
|
||||
'flow_table',
|
||||
'ipfix',
|
||||
'interface',
|
||||
'manager',
|
||||
'mirror',
|
||||
'netflow',
|
||||
'open_vswitch',
|
||||
'port',
|
||||
'qos',
|
||||
'queue',
|
||||
'ssl',
|
||||
'sflow',
|
||||
),
|
||||
'ovn-nbctl': (
|
||||
'acl',
|
||||
'address_set',
|
||||
'connection',
|
||||
'dhcp_options',
|
||||
'dns',
|
||||
'forwarding_group',
|
||||
'gateway_chassis',
|
||||
'ha_chassis',
|
||||
'ha_chassis_group',
|
||||
'load_balancer',
|
||||
'load_balancer_health_check',
|
||||
'logical_router',
|
||||
'logical_router_policy',
|
||||
'logical_router_port',
|
||||
'logical_router_static_route',
|
||||
'logical_switch',
|
||||
'logical_switch_port',
|
||||
'meter',
|
||||
'meter_band',
|
||||
'nat',
|
||||
'nb_global',
|
||||
'port_group',
|
||||
'qos',
|
||||
'ssl',
|
||||
),
|
||||
'ovn-sbctl': (
|
||||
'address_set',
|
||||
'chassis',
|
||||
'connection',
|
||||
'controller_event',
|
||||
'dhcp_options',
|
||||
'dhcpv6_options',
|
||||
'dns',
|
||||
'datapath_binding',
|
||||
'encap',
|
||||
'gateway_chassis',
|
||||
'ha_chassis',
|
||||
'ha_chassis_group',
|
||||
'igmp_group',
|
||||
'ip_multicast',
|
||||
'logical_flow',
|
||||
'mac_binding',
|
||||
'meter',
|
||||
'meter_band',
|
||||
'multicast_group',
|
||||
'port_binding',
|
||||
'port_group',
|
||||
'rbac_permission',
|
||||
'rbac_role',
|
||||
'sb_global',
|
||||
'ssl',
|
||||
'service_monitor',
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, tool):
|
||||
"""SimpleOVSDB constructor.
|
||||
|
||||
:param tool: Which tool with database commands to operate on.
|
||||
Usually one of `ovs-vsctl`, `ovn-nbctl`, `ovn-sbctl`
|
||||
:type tool: str
|
||||
:param table: Which table to operate on
|
||||
:type table: str
|
||||
"""
|
||||
if tool not in ('ovs-vsctl', 'ovn-nbctl', 'ovn-sbctl'):
|
||||
if tool not in self._tool_table_map:
|
||||
raise RuntimeError(
|
||||
"tool must be one of 'ovs-vsctl', 'ovn-nbctl', 'ovn-sbctl'")
|
||||
self.tool = tool
|
||||
self.tbl = table
|
||||
'tool must be one of "{}"'.format(self._tool_table_map.keys()))
|
||||
self._tool = tool
|
||||
|
||||
def _find_tbl(self, condition=None):
|
||||
"""Run and parse output of OVSDB `find` command.
|
||||
def __getattr__(self, table):
|
||||
if table not in self._tool_table_map[self._tool]:
|
||||
raise AttributeError(
|
||||
'table "{}" not known for use with "{}"'
|
||||
.format(table, self._tool))
|
||||
return self.Table(self._tool, table)
|
||||
|
||||
:param condition: An optional RFC 7047 5.1 match condition
|
||||
:type condition: Optional[str]
|
||||
:returns: Dictionary with data
|
||||
:rtype: Iterator[Dict[str, ANY]]
|
||||
class Table(object):
|
||||
"""Methods to interact with contents of OVSDB tables.
|
||||
|
||||
NOTE: At the time of this writing ``find`` is the only command
|
||||
line argument to OVSDB manipulating tools that actually supports
|
||||
JSON output.
|
||||
"""
|
||||
# When using json formatted output to OVS commands Internal OVSDB
|
||||
# notation may occur that require further deserializing.
|
||||
# Reference: https://tools.ietf.org/html/rfc7047#section-5.1
|
||||
ovs_type_cb_map = {
|
||||
'uuid': uuid.UUID,
|
||||
# FIXME sets also appear to sometimes contain type/value tuples
|
||||
'set': list,
|
||||
'map': dict,
|
||||
}
|
||||
cmd = [self.tool, '-f', 'json', 'find', self.tbl]
|
||||
if condition:
|
||||
cmd.append(condition)
|
||||
output = utils._run(*cmd)
|
||||
data = json.loads(output)
|
||||
for row in data['data']:
|
||||
values = []
|
||||
for col in row:
|
||||
if isinstance(col, list):
|
||||
f = ovs_type_cb_map.get(col[0], str)
|
||||
values.append(f(col[1]))
|
||||
else:
|
||||
values.append(col)
|
||||
yield dict(zip(data['headings'], values))
|
||||
|
||||
def __iter__(self):
|
||||
return self._find_tbl()
|
||||
def __init__(self, tool, table):
|
||||
"""SimpleOVSDBTable constructor.
|
||||
|
||||
def clear(self, rec, col):
|
||||
utils._run(self.tool, 'clear', self.tbl, rec, col)
|
||||
:param table: Which table to operate on
|
||||
:type table: str
|
||||
"""
|
||||
self._tool = tool
|
||||
self._table = table
|
||||
|
||||
def find(self, condition):
|
||||
return self._find_tbl(condition=condition)
|
||||
def _find_tbl(self, condition=None):
|
||||
"""Run and parse output of OVSDB `find` command.
|
||||
|
||||
def remove(self, rec, col, value):
|
||||
utils._run(self.tool, 'remove', self.tbl, rec, col, value)
|
||||
:param condition: An optional RFC 7047 5.1 match condition
|
||||
:type condition: Optional[str]
|
||||
:returns: Dictionary with data
|
||||
:rtype: Dict[str, any]
|
||||
"""
|
||||
# When using json formatted output to OVS commands Internal OVSDB
|
||||
# notation may occur that require further deserializing.
|
||||
# Reference: https://tools.ietf.org/html/rfc7047#section-5.1
|
||||
ovs_type_cb_map = {
|
||||
'uuid': uuid.UUID,
|
||||
# FIXME sets also appear to sometimes contain type/value tuples
|
||||
'set': list,
|
||||
'map': dict,
|
||||
}
|
||||
cmd = [self._tool, '-f', 'json', 'find', self._table]
|
||||
if condition:
|
||||
cmd.append(condition)
|
||||
output = utils._run(*cmd)
|
||||
data = json.loads(output)
|
||||
for row in data['data']:
|
||||
values = []
|
||||
for col in row:
|
||||
if isinstance(col, list):
|
||||
f = ovs_type_cb_map.get(col[0], str)
|
||||
values.append(f(col[1]))
|
||||
else:
|
||||
values.append(col)
|
||||
yield dict(zip(data['headings'], values))
|
||||
|
||||
def set(self, rec, col, value):
|
||||
utils._run(self.tool, 'set', self.tbl, rec, '{}={}'.format(col, value))
|
||||
def __iter__(self):
|
||||
return self._find_tbl()
|
||||
|
||||
def clear(self, rec, col):
|
||||
utils._run(self._tool, 'clear', self._table, rec, col)
|
||||
|
||||
def find(self, condition):
|
||||
return self._find_tbl(condition=condition)
|
||||
|
||||
def remove(self, rec, col, value):
|
||||
utils._run(self._tool, 'remove', self._table, rec, col, value)
|
||||
|
||||
def set(self, rec, col, value):
|
||||
utils._run(self._tool, 'set', self._table, rec,
|
||||
'{}={}'.format(col, value))
|
||||
|
|
|
@ -13,13 +13,17 @@
|
|||
# limitations under the License.
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import enum
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import time
|
||||
|
||||
from base64 import b64decode
|
||||
from subprocess import check_call, CalledProcessError
|
||||
|
||||
|
@ -50,7 +54,8 @@ from charmhelpers.core.hookenv import (
|
|||
INFO,
|
||||
ERROR,
|
||||
status_set,
|
||||
network_get_primary_address
|
||||
network_get_primary_address,
|
||||
WARNING,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
import psutil
|
||||
except ImportError:
|
||||
|
@ -263,6 +275,12 @@ class SharedDBContext(OSContextGenerator):
|
|||
'database_password': rdata.get(password_setting),
|
||||
'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':
|
||||
ctxt['database_type'] = 'mysql'
|
||||
if self.context_complete(ctxt):
|
||||
|
@ -2396,3 +2414,734 @@ class DHCPAgentContext(OSContextGenerator):
|
|||
return False
|
||||
else:
|
||||
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
|
||||
|
|
|
@ -36,6 +36,7 @@ from charmhelpers.contrib.network import ip
|
|||
from charmhelpers.core import unitdata
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
WORKLOAD_STATES,
|
||||
action_fail,
|
||||
action_set,
|
||||
config,
|
||||
|
@ -1819,6 +1820,16 @@ def os_application_version_set(package):
|
|||
application_version_set(application_version)
|
||||
|
||||
|
||||
def os_application_status_set(check_function):
|
||||
"""Run the supplied function and set the application status accordingly.
|
||||
|
||||
:param check_function: Function to run to get app states and messages.
|
||||
:type check_function: function
|
||||
"""
|
||||
state, message = check_function()
|
||||
status_set(state, message, application=True)
|
||||
|
||||
|
||||
def enable_memcache(source=None, release=None, package=None):
|
||||
"""Determine if memcache should be enabled on the local unit
|
||||
|
||||
|
@ -2283,36 +2294,59 @@ def check_api_unit_ready(check_db_ready=True):
|
|||
:returns: Whether unit state is ready and status message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
unit_ready = True
|
||||
msg = ''
|
||||
unit_state, msg = get_api_unit_status(check_db_ready=check_db_ready)
|
||||
return unit_state == WORKLOAD_STATES.ACTIVE, msg
|
||||
|
||||
|
||||
def get_api_unit_status(check_db_ready=True):
|
||||
"""Return a workload status and message for this unit.
|
||||
|
||||
:param check_db_ready: Include checks of database readiness.
|
||||
:type check_db_ready: bool
|
||||
:returns: Workload state and message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
unit_state = WORKLOAD_STATES.ACTIVE
|
||||
msg = 'Unit is ready'
|
||||
if is_db_maintenance_mode():
|
||||
unit_state = WORKLOAD_STATES.MAINTENANCE
|
||||
msg = 'Database in maintenance mode.'
|
||||
elif is_unit_paused_set():
|
||||
unit_state = WORKLOAD_STATES.BLOCKED
|
||||
msg = 'Unit paused.'
|
||||
elif check_db_ready and not is_db_ready():
|
||||
unit_state = WORKLOAD_STATES.WAITING
|
||||
msg = 'Allowed_units list provided but this unit not present'
|
||||
elif not is_db_initialised():
|
||||
unit_state = WORKLOAD_STATES.WAITING
|
||||
msg = 'Database not initialised'
|
||||
elif not is_expected_scale():
|
||||
unit_state = WORKLOAD_STATES.WAITING
|
||||
msg = 'Charm and its dependencies not yet at expected scale'
|
||||
if msg:
|
||||
unit_ready = False
|
||||
else:
|
||||
msg = 'Unit has passed checks and is ready'
|
||||
juju_log(msg, 'DEBUG')
|
||||
return unit_ready, msg
|
||||
return unit_state, msg
|
||||
|
||||
|
||||
def check_api_application_ready():
|
||||
"""Check if this application is ready.
|
||||
|
||||
:returns: Whether unit state is ready and status message
|
||||
:returns: Whether application state is ready and status message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
unit_ready, msg = check_api_unit_ready(check_db_ready=True)
|
||||
if not unit_ready:
|
||||
return unit_ready, msg
|
||||
if are_peers_ready():
|
||||
return True, 'All units have passed checks and are ready'
|
||||
else:
|
||||
return False, 'This unit is ready but peers are not'
|
||||
app_state, msg = get_api_application_status()
|
||||
return app_state == WORKLOAD_STATES.ACTIVE, msg
|
||||
|
||||
|
||||
def get_api_application_status():
|
||||
"""Return a workload status and message for this application.
|
||||
|
||||
:returns: Workload state and message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
app_state, msg = get_api_unit_status()
|
||||
if app_state == WORKLOAD_STATES.ACTIVE:
|
||||
if are_peers_ready():
|
||||
return WORKLOAD_STATES.ACTIVE, 'Application Ready'
|
||||
else:
|
||||
return WORKLOAD_STATES.WAITING, 'Some units are not ready'
|
||||
return app_state, msg
|
||||
|
|
|
@ -163,7 +163,16 @@ def retrieve_secret_id(url, token):
|
|||
:returns: secret_id to use for Vault Access
|
||||
:rtype: str"""
|
||||
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')
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
from __future__ import print_function
|
||||
import copy
|
||||
from distutils.version import LooseVersion
|
||||
from enum import Enum
|
||||
from functools import wraps
|
||||
from collections import namedtuple
|
||||
import glob
|
||||
|
@ -57,6 +58,14 @@ RANGE_WARNING = ('Passing NO_PROXY string that includes a cidr. '
|
|||
'This may not be compatible with software you are '
|
||||
'running in your shell.')
|
||||
|
||||
|
||||
class WORKLOAD_STATES(Enum):
|
||||
ACTIVE = 'active'
|
||||
BLOCKED = 'blocked'
|
||||
MAINTENANCE = 'maintenance'
|
||||
WAITING = 'waiting'
|
||||
|
||||
|
||||
cache = {}
|
||||
|
||||
|
||||
|
@ -1088,22 +1097,33 @@ def function_tag():
|
|||
return os.environ.get('JUJU_FUNCTION_TAG') or action_tag()
|
||||
|
||||
|
||||
def status_set(workload_state, message):
|
||||
def status_set(workload_state, message, application=False):
|
||||
"""Set the workload state with a message
|
||||
|
||||
Use status-set to set the workload state with a message which is visible
|
||||
to the user via juju status. If the status-set command is not found then
|
||||
assume this is juju < 1.23 and juju-log the message instead.
|
||||
|
||||
workload_state -- valid juju workload state.
|
||||
message -- status update message
|
||||
workload_state -- valid juju workload state. str or WORKLOAD_STATES
|
||||
message -- status update message
|
||||
application -- Whether this is an application state set
|
||||
"""
|
||||
valid_states = ['maintenance', 'blocked', 'waiting', 'active']
|
||||
if workload_state not in valid_states:
|
||||
raise ValueError(
|
||||
'{!r} is not a valid workload state'.format(workload_state)
|
||||
)
|
||||
cmd = ['status-set', workload_state, message]
|
||||
bad_state_msg = '{!r} is not a valid workload state'
|
||||
|
||||
if isinstance(workload_state, str):
|
||||
try:
|
||||
# Convert string to enum.
|
||||
workload_state = WORKLOAD_STATES[workload_state.upper()]
|
||||
except KeyError:
|
||||
raise ValueError(bad_state_msg.format(workload_state))
|
||||
|
||||
if workload_state not in WORKLOAD_STATES:
|
||||
raise ValueError(bad_state_msg.format(workload_state))
|
||||
|
||||
cmd = ['status-set']
|
||||
if application:
|
||||
cmd.append('--application')
|
||||
cmd.extend([workload_state.value, message])
|
||||
try:
|
||||
ret = subprocess.call(cmd)
|
||||
if ret == 0:
|
||||
|
@ -1111,7 +1131,7 @@ def status_set(workload_state, message):
|
|||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
log_message = 'status-set failed: {} {}'.format(workload_state,
|
||||
log_message = 'status-set failed: {} {}'.format(workload_state.value,
|
||||
message)
|
||||
log(log_message, level='INFO')
|
||||
|
||||
|
|
|
@ -17,14 +17,17 @@
|
|||
|
||||
import yaml
|
||||
|
||||
from subprocess import check_call
|
||||
from subprocess import check_call, CalledProcessError
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
DEBUG,
|
||||
ERROR,
|
||||
WARNING,
|
||||
)
|
||||
|
||||
from charmhelpers.core.host import is_container
|
||||
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
|
||||
|
@ -62,4 +65,11 @@ def create(sysctl_dict, sysctl_file, ignore=False):
|
|||
if ignore:
|
||||
call.append("-e")
|
||||
|
||||
check_call(call)
|
||||
try:
|
||||
check_call(call)
|
||||
except CalledProcessError as e:
|
||||
if is_container():
|
||||
log("Error setting some sysctl keys in this container: {}".format(e.output),
|
||||
level=WARNING)
|
||||
else:
|
||||
raise e
|
||||
|
|
Loading…
Reference in New Issue