|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# Copyright (c) 2020 Red Hat, Inc.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# This file is part of nmstate
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# This program is free software: you can redistribute it and/or modify
|
|
Packit Service |
0535c1 |
# it under the terms of the GNU Lesser General Public License as published by
|
|
Packit Service |
0535c1 |
# the Free Software Foundation, either version 2.1 of the License, or
|
|
Packit Service |
0535c1 |
# (at your option) any later version.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# This program is distributed in the hope that it will be useful,
|
|
Packit Service |
0535c1 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit Service |
0535c1 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit Service |
0535c1 |
# GNU Lesser General Public License for more details.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# You should have received a copy of the GNU Lesser General Public License
|
|
Packit Service |
0535c1 |
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
import datetime
|
|
Packit Service |
0535c1 |
import logging
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
from libnmstate.error import NmstateInternalError
|
|
Packit Service |
0535c1 |
from libnmstate.error import NmstateTimeoutError
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
from .common import NM
|
|
Packit Service |
0535c1 |
from .common import GLib
|
|
Packit Service |
0535c1 |
from .common import Gio
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
# Interval for idle checker to check on whether timeout should trigger since
|
|
Packit Service |
0535c1 |
# last finish async action.
|
|
Packit Service |
0535c1 |
IDLE_CHECK_INTERNAL = 5
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
# libnm dbus connection has reply timeout 25 seconds.
|
|
Packit Service |
0535c1 |
IDLE_TIMEOUT = 25
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
# NetworkManage is using dbus in libnm while the dbus has limitation on
|
|
Packit Service |
0535c1 |
# maximum number of pending replies per connection.(RHEL/CentOS 8 is 1024)
|
|
Packit Service |
0535c1 |
# Hence limit the synchronous queue size
|
|
Packit Service |
0535c1 |
SLOW_ASYNC_QUEUE_SIZE = 100
|
|
Packit Service |
0535c1 |
FAST_ASYNC_QUEUE_SIZE = 300
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
class NmContext:
|
|
Packit Service |
0535c1 |
def __init__(self):
|
|
Packit Service |
0535c1 |
self._client = NM.Client.new(cancellable=None)
|
|
Packit Service |
0535c1 |
self._context = self._client.get_main_context()
|
|
Packit Service |
0535c1 |
self._quitting = False
|
|
Packit Service |
0535c1 |
self._cancellable = None
|
|
Packit Service |
0535c1 |
self._error = None
|
|
Packit Service |
0535c1 |
self._timeout_source = None
|
|
Packit Service |
0535c1 |
self._last_async_finish_time = None
|
|
Packit Service |
0535c1 |
self._fast_queue = None
|
|
Packit Service |
0535c1 |
self._slow_queue = None
|
|
Packit Service |
0535c1 |
self._init_queue()
|
|
Packit Service |
0535c1 |
self._init_cancellable()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _init_queue(self):
|
|
Packit Service |
0535c1 |
self._fast_queue = set()
|
|
Packit Service |
0535c1 |
self._slow_queue = set()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _init_cancellable(self):
|
|
Packit Service |
0535c1 |
self._cancellable = Gio.Cancellable.new()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
@property
|
|
Packit Service |
0535c1 |
def cancellable(self):
|
|
Packit Service |
0535c1 |
return self._cancellable
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
@property
|
|
Packit Service |
0535c1 |
def client(self):
|
|
Packit Service |
0535c1 |
if self._quitting:
|
|
Packit Service |
0535c1 |
return None
|
|
Packit Service |
0535c1 |
return self._client
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
@property
|
|
Packit Service |
0535c1 |
def context(self):
|
|
Packit Service |
0535c1 |
if not self._context:
|
|
Packit Service |
0535c1 |
raise NmstateInternalError(
|
|
Packit Service |
0535c1 |
"BUG: Accessing MainContext while it is None"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
return self._context
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def refresh_content(self):
|
|
Packit Service |
0535c1 |
if self.context:
|
|
Packit Service |
0535c1 |
while self.context.iteration(False):
|
|
Packit Service |
0535c1 |
pass
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def clean_up(self):
|
|
Packit Service |
0535c1 |
if self._cancellable:
|
|
Packit Service |
0535c1 |
self._cancellable.cancel()
|
|
Packit Service |
0535c1 |
self._del_timeout()
|
|
Packit Service |
0535c1 |
self._del_client()
|
|
Packit Service |
0535c1 |
self._context = None
|
|
Packit Service |
0535c1 |
self._cancellable = None
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _del_client(self):
|
|
Packit Service |
0535c1 |
if self._client:
|
|
Packit Service |
0535c1 |
is_done = []
|
|
Packit Service |
0535c1 |
is_timeout = []
|
|
Packit Service |
0535c1 |
self._client.get_context_busy_watcher().weak_ref(
|
|
Packit Service |
0535c1 |
lambda: is_done.append(1)
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
self._client = None
|
|
Packit Service |
0535c1 |
self._quitting = True
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
self.refresh_content()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
if not is_done:
|
|
Packit Service |
0535c1 |
timeout_source = GLib.timeout_source_new(50)
|
|
Packit Service |
0535c1 |
try:
|
|
Packit Service |
0535c1 |
timeout_source.set_callback(lambda x: is_timeout.append(1))
|
|
Packit Service |
0535c1 |
timeout_source.attach(self.context)
|
|
Packit Service |
0535c1 |
while not is_done and not is_timeout:
|
|
Packit Service |
0535c1 |
self.context.iteration(True)
|
|
Packit Service |
0535c1 |
finally:
|
|
Packit Service |
0535c1 |
timeout_source.destroy()
|
|
Packit Service |
0535c1 |
if not is_done:
|
|
Packit Service |
0535c1 |
logging.error("BUG: NM.Client is not cleaned")
|
|
Packit Service |
0535c1 |
self._context = None
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _del_timeout(self):
|
|
Packit Service |
0535c1 |
if self._timeout_source:
|
|
Packit Service |
0535c1 |
self._timeout_source.destroy()
|
|
Packit Service |
0535c1 |
self._timeout_source = None
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def register_async(self, action, fast=False):
|
|
Packit Service |
0535c1 |
"""
|
|
Packit Service |
0535c1 |
Register action(string) to wait list.
|
|
Packit Service |
0535c1 |
Set fast as True if requested action does not require too much time,
|
|
Packit Service |
0535c1 |
for example: profile modification.
|
|
Packit Service |
0535c1 |
"""
|
|
Packit Service |
0535c1 |
queue = self._fast_queue if fast else self._slow_queue
|
|
Packit Service |
0535c1 |
max_queue = FAST_ASYNC_QUEUE_SIZE if fast else SLOW_ASYNC_QUEUE_SIZE
|
|
Packit Service |
0535c1 |
if len(queue) >= max_queue:
|
|
Packit Service |
0535c1 |
logging.debug(
|
|
Packit Service |
0535c1 |
f"Async queue({max_queue}) full, waiting all existing actions "
|
|
Packit Service |
0535c1 |
"to be finished before registering more async action"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
# TODO: No need to wait all finish, should continue when the queue
|
|
Packit Service |
0535c1 |
# is considerably empty and ready for new async action.
|
|
Packit Service |
0535c1 |
self.wait_all_finish()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
if action in self._fast_queue or action in self._slow_queue:
|
|
Packit Service |
0535c1 |
raise NmstateInternalError(
|
|
Packit Service |
0535c1 |
f"BUG: An existing actions {action} is already registered"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
logging.debug(f"Async action: {action} started")
|
|
Packit Service |
0535c1 |
queue.add(action)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def finish_async(self, action, suppress_log=False):
|
|
Packit Service |
0535c1 |
"""
|
|
Packit Service |
0535c1 |
Mark action(string) as finished.
|
|
Packit Service |
0535c1 |
"""
|
|
Packit Service |
0535c1 |
self._last_async_finish_time = datetime.datetime.now()
|
|
Packit Service |
0535c1 |
if not suppress_log:
|
|
Packit Service |
0535c1 |
logging.debug(f"Async action: {action} finished")
|
|
Packit Service |
0535c1 |
self._fast_queue.discard(action)
|
|
Packit Service |
0535c1 |
self._slow_queue.discard(action)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _action_all_finished(self):
|
|
Packit Service |
0535c1 |
return not (len(self._fast_queue) or len(self._slow_queue))
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _idle_timeout_cb(self, _user_data):
|
|
Packit Service |
0535c1 |
if self._error or self._action_all_finished():
|
|
Packit Service |
0535c1 |
return GLib.SOURCE_REMOVE
|
|
Packit Service |
0535c1 |
idle_time = datetime.datetime.now() - self._last_async_finish_time
|
|
Packit Service |
0535c1 |
if idle_time > datetime.timedelta(seconds=IDLE_TIMEOUT):
|
|
Packit Service |
0535c1 |
remaining_actions = self._slow_queue | self._fast_queue
|
|
Packit Service |
0535c1 |
self.fail(
|
|
Packit Service |
0535c1 |
NmstateTimeoutError(f"Action {remaining_actions} timeout")
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
return GLib.SOURCE_REMOVE
|
|
Packit Service |
0535c1 |
else:
|
|
Packit Service |
0535c1 |
return GLib.SOURCE_CONTINUE
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def is_cancelled(self):
|
|
Packit Service |
0535c1 |
return self._cancellable.is_cancelled()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def fail(self, exception):
|
|
Packit Service |
0535c1 |
if not self._cancellable.is_cancelled():
|
|
Packit Service |
0535c1 |
if self._error:
|
|
Packit Service |
0535c1 |
logging.error(
|
|
Packit Service |
0535c1 |
f"BUG: There is already a exception assigned: "
|
|
Packit Service |
0535c1 |
f"existing: {self._error}, new exception {exception}"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
self.cancellable.cancel()
|
|
Packit Service |
0535c1 |
self._del_timeout()
|
|
Packit Service |
0535c1 |
self._error = exception
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def wait_all_finish(self):
|
|
Packit Service |
0535c1 |
"""
|
|
Packit Service |
0535c1 |
Block till all async actions been marked as finished via
|
|
Packit Service |
0535c1 |
`finish_async()` or anyone failed by `fail()`.
|
|
Packit Service |
0535c1 |
"""
|
|
Packit Service |
0535c1 |
self._last_async_finish_time = datetime.datetime.now()
|
|
Packit Service |
0535c1 |
if not self._action_all_finished():
|
|
Packit Service |
0535c1 |
self._timeout_source = GLib.timeout_source_new(
|
|
Packit Service |
0535c1 |
IDLE_CHECK_INTERNAL * 1000
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
user_data = None
|
|
Packit Service |
0535c1 |
self._timeout_source.set_callback(self._idle_timeout_cb, user_data)
|
|
Packit Service |
0535c1 |
self._timeout_source.attach(self._context)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
while not self._action_all_finished() and not self._error:
|
|
Packit Service |
0535c1 |
self.context.iteration(True)
|
|
Packit Service |
0535c1 |
self._del_timeout()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
if self._error:
|
|
Packit Service |
0535c1 |
# The queue and error should be flush and perpare for another run
|
|
Packit Service |
0535c1 |
self._init_queue()
|
|
Packit Service |
0535c1 |
self._init_cancellable()
|
|
Packit Service |
0535c1 |
tmp_error = self._error
|
|
Packit Service |
0535c1 |
self._error = None
|
|
Packit Service |
0535c1 |
# pylint: disable=raising-bad-type
|
|
Packit Service |
0535c1 |
raise tmp_error
|
|
Packit Service |
0535c1 |
# pylint: enable=raising-bad-type
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def get_nm_dev(self, iface_name):
|
|
Packit Service |
0535c1 |
return self.client.get_device_by_iface(iface_name)
|