241 lines
8.7 KiB
Python
241 lines
8.7 KiB
Python
# Copyright 2019 Canonical Ltd
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
import os
|
|
import subprocess
|
|
import uuid
|
|
|
|
import utils
|
|
|
|
|
|
OVN_RUNDIR = '/var/run/ovn'
|
|
OVN_SYSCONFDIR = '/etc/ovn'
|
|
|
|
|
|
def ovn_appctl(target, args, rundir=None, use_ovs_appctl=False,
|
|
cmd_executor=None):
|
|
"""Run ovn/ovs-appctl for target with args and return output.
|
|
|
|
:param target: Name of daemon to contact. Unless target begins with '/',
|
|
`ovn-appctl` looks for a pidfile and will build the path to
|
|
a /var/run/ovn/target.pid.ctl for you.
|
|
:type target: str
|
|
:param args: Command and arguments to pass to `ovn-appctl`
|
|
:type args: Tuple[str, ...]
|
|
:param rundir: Override path to sockets
|
|
:type rundir: Optional[str]
|
|
:param use_ovs_appctl: The ``ovn-appctl`` command appeared in OVN 20.03,
|
|
set this to True to use ``ovs-appctl`` instead.
|
|
:type use_ovs_appctl: bool
|
|
:returns: Output from command
|
|
:rtype: str
|
|
:raises: subprocess.CalledProcessError
|
|
"""
|
|
# NOTE(fnordahl): The ovsdb-server processes for the OVN databases use a
|
|
# non-standard naming scheme for their daemon control socket and we need
|
|
# to pass the full path to the socket.
|
|
if target in ('ovnnb_db', 'ovnsb_db',):
|
|
target = os.path.join(rundir or OVN_RUNDIR, target + '.ctl')
|
|
|
|
if use_ovs_appctl:
|
|
tool = 'ovs-appctl'
|
|
else:
|
|
tool = 'ovn-appctl'
|
|
|
|
if not cmd_executor:
|
|
cmd_executor = utils._run
|
|
return cmd_executor(tool, '-t', target, *args)
|
|
|
|
|
|
class OVNClusterStatus(object):
|
|
|
|
def __init__(self, name, cluster_id, server_id, address, status, role,
|
|
term, leader, vote, election_timer, log,
|
|
entries_not_yet_committed, entries_not_yet_applied,
|
|
connections, servers):
|
|
"""Initialize and populate OVNClusterStatus object.
|
|
|
|
Use class initializer so we can define types in a compatible manner.
|
|
|
|
:param name: Name of schema used for database
|
|
:type name: str
|
|
:param cluster_id: UUID of cluster
|
|
:type cluster_id: uuid.UUID
|
|
:param server_id: UUID of server
|
|
:type server_id: uuid.UUID
|
|
:param address: OVSDB connection method
|
|
:type address: str
|
|
:param status: Status text
|
|
:type status: str
|
|
:param role: Role of server
|
|
:type role: str
|
|
:param term: Election term
|
|
:type term: int
|
|
:param leader: Short form UUID of leader
|
|
:type leader: str
|
|
:param vote: Vote
|
|
:type vote: str
|
|
:param election_timer: Current value of election timer
|
|
:type election_timer: int
|
|
:param log: Log
|
|
:type log: str
|
|
:param entries_not_yet_committed: Entries not yet committed
|
|
:type entries_not_yet_committed: int
|
|
:param entries_not_yet_applied: Entries not yet applied
|
|
:type entries_not_yet_applied: int
|
|
:param connections: Connections
|
|
:type connections: str
|
|
:param servers: Servers in the cluster
|
|
[('0ea6', 'ssl:192.0.2.42:6643')]
|
|
:type servers: List[Tuple[str,str]]
|
|
"""
|
|
self.name = name
|
|
self.cluster_id = cluster_id
|
|
self.server_id = server_id
|
|
self.address = address
|
|
self.status = status
|
|
self.role = role
|
|
self.term = term
|
|
self.leader = leader
|
|
self.vote = vote
|
|
self.election_timer = election_timer
|
|
self.log = log
|
|
self.entries_not_yet_committed = entries_not_yet_committed
|
|
self.entries_not_yet_applied = entries_not_yet_applied
|
|
self.connections = connections
|
|
self.servers = servers
|
|
|
|
def __eq__(self, other):
|
|
return (
|
|
self.name == other.name and
|
|
self.cluster_id == other.cluster_id and
|
|
self.server_id == other.server_id and
|
|
self.address == other.address and
|
|
self.status == other.status and
|
|
self.role == other.role and
|
|
self.term == other.term and
|
|
self.leader == other.leader and
|
|
self.vote == other.vote and
|
|
self.election_timer == other.election_timer and
|
|
self.log == other.log and
|
|
self.entries_not_yet_committed ==
|
|
other.entries_not_yet_committed and
|
|
self.entries_not_yet_applied == other.entries_not_yet_applied and
|
|
self.connections == other.connections and
|
|
self.servers == other.servers)
|
|
|
|
@property
|
|
def is_cluster_leader(self):
|
|
"""Retrieve status information from clustered OVSDB.
|
|
|
|
:returns: Whether target is cluster leader
|
|
:rtype: bool
|
|
"""
|
|
return self.leader == 'self'
|
|
|
|
|
|
def cluster_status(target, schema=None, use_ovs_appctl=False, rundir=None,
|
|
cmd_executor=None):
|
|
"""Retrieve status information from clustered OVSDB.
|
|
|
|
:param target: Usually one of 'ovsdb-server', 'ovnnb_db', 'ovnsb_db', can
|
|
also be full path to control socket.
|
|
:type target: str
|
|
:param schema: Database schema name, deduced from target if not provided
|
|
:type schema: Optional[str]
|
|
:param use_ovs_appctl: The ``ovn-appctl`` command appeared in OVN 20.03,
|
|
set this to True to use ``ovs-appctl`` instead.
|
|
:type use_ovs_appctl: bool
|
|
:param rundir: Override path to sockets
|
|
:type rundir: Optional[str]
|
|
:returns: cluster status data object
|
|
:rtype: OVNClusterStatus
|
|
:raises: subprocess.CalledProcessError, KeyError, RuntimeError
|
|
"""
|
|
schema_map = {
|
|
'ovnnb_db': 'OVN_Northbound',
|
|
'ovnsb_db': 'OVN_Southbound',
|
|
}
|
|
if schema and schema not in schema_map.keys():
|
|
raise RuntimeError('Unknown schema provided: "{}"'.format(schema))
|
|
|
|
status = {}
|
|
k = ''
|
|
for line in ovn_appctl(target,
|
|
('cluster/status', schema or schema_map[target]),
|
|
rundir=rundir,
|
|
use_ovs_appctl=use_ovs_appctl,
|
|
cmd_executor=cmd_executor).splitlines():
|
|
if k and line.startswith(' '):
|
|
# there is no key which means this is a instance of a multi-line/
|
|
# multi-value item, populate the List which is already stored under
|
|
# the key.
|
|
if k == 'servers':
|
|
status[k].append(
|
|
tuple(line.replace(')', '').lstrip().split()[0:4:3]))
|
|
else:
|
|
status[k].append(line.lstrip())
|
|
elif ':' in line:
|
|
# this is a line with a key
|
|
k, v = line.split(':', 1)
|
|
k = k.lower()
|
|
k = k.replace(' ', '_')
|
|
if v:
|
|
# this is a line with both key and value
|
|
if k in ('cluster_id', 'server_id',):
|
|
v = v.replace('(', '')
|
|
v = v.replace(')', '')
|
|
status[k] = tuple(v.split())
|
|
else:
|
|
status[k] = v.lstrip()
|
|
else:
|
|
# this is a line with only key which means a multi-line/
|
|
# multi-value item. Store key as List which will be
|
|
# populated on subsequent iterations.
|
|
status[k] = []
|
|
return OVNClusterStatus(
|
|
status['name'],
|
|
uuid.UUID(status['cluster_id'][1]),
|
|
uuid.UUID(status['server_id'][1]),
|
|
status['address'],
|
|
status['status'],
|
|
status['role'],
|
|
int(status['term']),
|
|
status['leader'],
|
|
status['vote'],
|
|
int(status['election_timer']),
|
|
status['log'],
|
|
int(status['entries_not_yet_committed']),
|
|
int(status['entries_not_yet_applied']),
|
|
status['connections'],
|
|
status['servers'])
|
|
|
|
|
|
def is_northd_active(cmd_executor=None):
|
|
"""Query `ovn-northd` for active status.
|
|
|
|
Note that the active status information for ovn-northd is available for
|
|
OVN 20.03 and onward.
|
|
|
|
:returns: True if local `ovn-northd` instance is active, False otherwise
|
|
:rtype: bool
|
|
"""
|
|
try:
|
|
for line in ovn_appctl('ovn-northd', ('status',),
|
|
cmd_executor=cmd_executor).splitlines():
|
|
if line.startswith('Status:') and 'active' in line:
|
|
return True
|
|
except subprocess.CalledProcessError:
|
|
pass
|
|
return False
|