compat: Save server's API version in for pre-schema servers

When client comunicates with server that doesn't support 'schema'
command it needs to determine its api version to be able to use the
right compat code. Storing information about server version reduces the
need to call 'env' or 'ping' command only to first time the server is
contacted.

https://fedorahosted.org/freeipa/ticket/6069

Reviewed-By: Jan Cholasta <jcholast@redhat.com>
This commit is contained in:
David Kupka 2016-07-26 13:35:22 +02:00 committed by Jan Cholasta
parent e76b0bbbcc
commit 229e2a1ed9
4 changed files with 112 additions and 94 deletions

View File

@ -2,23 +2,90 @@
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license # Copyright (C) 2016 FreeIPA Contributors see COPYING for license
# #
import collections
import errno
import json
import os
from . import compat from . import compat
from . import schema from . import schema
from ipaclient.plugins.rpcclient import rpcclient from ipaclient.plugins.rpcclient import rpcclient
from ipaplatform.paths import paths
from ipapython.dnsutil import DNSName
from ipapython.ipa_log_manager import log_mgr
logger = log_mgr.get_logger(__name__)
class ServerInfo(collections.MutableMapping):
_DIR = os.path.join(paths.USER_CACHE_PATH, 'ipa', 'servers')
def __init__(self, api):
hostname = DNSName(api.env.server).ToASCII()
self._path = os.path.join(self._DIR, hostname)
self._dict = {}
self._dirty = False
self._read()
def __enter__(self):
return self
def __exit__(self, *_exc_info):
if self._dirty:
self._write()
def _read(self):
try:
with open(self._path, 'r') as sc:
self._dict = json.load(sc)
except EnvironmentError as e:
if e.errno != errno.ENOENT:
logger.warning('Failed to read server info: {}'.format(e))
def _write(self):
try:
try:
os.makedirs(self._DIR)
except EnvironmentError as e:
if e.errno != errno.EEXIST:
raise
with open(self._path, 'w') as sc:
json.dump(self._dict, sc)
except EnvironmentError as e:
logger.warning('Failed to write server info: {}'.format(e))
def __getitem__(self, key):
return self._dict[key]
def __setitem__(self, key, value):
self._dirty = key not in self._dict or self._dict[key] != value
self._dict[key] = value
def __delitem__(self, key):
del self._dict[key]
self._dirty = True
def __iter__(self):
return iter(self._dict)
def __len__(self):
return len(self._dict)
def get_package(api): def get_package(api):
if api.env.in_tree: if api.env.in_tree:
from ipaserver import plugins from ipaserver import plugins
else: else:
client = rpcclient(api) with ServerInfo(api) as server_info:
client.finalize() client = rpcclient(api)
try: client.finalize()
plugins = schema.get_package(api, client) try:
except schema.NotAvailable: plugins = schema.get_package(api, server_info, client)
plugins = compat.get_package(api, client) except schema.NotAvailable:
finally: plugins = compat.get_package(api, server_info, client)
if client.isconnected(): finally:
client.disconnect() if client.isconnected():
client.disconnect()
return plugins return plugins

View File

@ -31,23 +31,26 @@ class CompatObject(Object):
pass pass
def get_package(api, client): def get_package(api, server_info, client):
if not client.isconnected():
client.connect(verbose=False)
env = client.forward(u'env', u'api_version', version=u'2.0')
try: try:
server_version = env['result']['api_version'] server_version = server_info['version']
except KeyError: except KeyError:
ping = client.forward(u'ping', version=u'2.0') if not client.isconnected():
client.connect(verbose=False)
env = client.forward(u'env', u'api_version', version=u'2.0')
try: try:
match = re.search(u'API version (2\.[0-9]+)', ping['summary']) server_version = env['result']['api_version']
except KeyError: except KeyError:
match = None ping = client.forward(u'ping', u'api_version', version=u'2.0')
if match is not None: try:
server_version = match.group(1) match = re.search(u'API version (2\.[0-9]+)', ping['summary'])
else: except KeyError:
server_version = u'2.0' match = None
if match is not None:
server_version = match.group(1)
else:
server_version = u'2.0'
server_info['version'] = server_version
server_version = LooseVersion(server_version) server_version = LooseVersion(server_version)
package_names = {} package_names = {}

View File

@ -19,6 +19,7 @@ from ipalib import errors, parameters, plugable
from ipalib.frontend import Object from ipalib.frontend import Object
from ipalib.output import Output from ipalib.output import Output
from ipalib.parameters import DefaultFrom, Flag, Password, Str from ipalib.parameters import DefaultFrom, Flag, Password, Str
from ipaplatform.paths import paths
from ipapython.dn import DN from ipapython.dn import DN
from ipapython.dnsutil import DNSName from ipapython.dnsutil import DNSName
from ipapython.ipa_log_manager import log_mgr from ipapython.ipa_log_manager import log_mgr
@ -55,17 +56,6 @@ _PARAMS = {
'str': parameters.Str, 'str': parameters.Str,
} }
USER_CACHE_PATH = (
os.environ.get('XDG_CACHE_HOME') or
os.path.join(
os.environ.get(
'HOME',
os.path.expanduser('~')
),
'.cache'
)
)
logger = log_mgr.get_logger(__name__) logger = log_mgr.get_logger(__name__)
@ -359,62 +349,6 @@ class NotAvailable(Exception):
pass pass
class ServerInfo(collections.MutableMapping):
_DIR = os.path.join(USER_CACHE_PATH, 'ipa', 'servers')
def __init__(self, api):
hostname = DNSName(api.env.server).ToASCII()
self._path = os.path.join(self._DIR, hostname)
self._dict = {}
self._dirty = False
self._read()
def __enter__(self):
return self
def __exit__(self, *_exc_info):
if self._dirty:
self._write()
def _read(self):
try:
with open(self._path, 'r') as sc:
self._dict = json.load(sc)
except EnvironmentError as e:
if e.errno != errno.ENOENT:
logger.warning('Failed to read server info: {}'.format(e))
def _write(self):
try:
try:
os.makedirs(self._DIR)
except EnvironmentError as e:
if e.errno != errno.EEXIST:
raise
with open(self._path, 'w') as sc:
json.dump(self._dict, sc)
except EnvironmentError as e:
logger.warning('Failed to write server info: {}'.format(e))
def __getitem__(self, key):
return self._dict[key]
def __setitem__(self, key, value):
self._dirty = key not in self._dict or self._dict[key] != value
self._dict[key] = value
def __delitem__(self, key):
del self._dict[key]
self._dirty = True
def __iter__(self):
return iter(self._dict)
def __len__(self):
return len(self._dict)
class Schema(object): class Schema(object):
""" """
Store and provide schema for commands and topics Store and provide schema for commands and topics
@ -436,7 +370,7 @@ class Schema(object):
""" """
namespaces = {'classes', 'commands', 'topics'} namespaces = {'classes', 'commands', 'topics'}
_DIR = os.path.join(USER_CACHE_PATH, 'ipa', 'schema') _DIR = os.path.join(paths.USER_CACHE_PATH, 'ipa', 'schema')
def __init__(self, api, server_info, client): def __init__(self, api, server_info, client):
self._dict = {} self._dict = {}
@ -618,13 +552,12 @@ class Schema(object):
self._dict[ns][member].update(self._help[ns][member]) self._dict[ns][member].update(self._help[ns][member])
def get_package(api, client): def get_package(api, server_info, client):
try: try:
schema = api._schema schema = api._schema
except AttributeError: except AttributeError:
with ServerInfo(api.env.hostname) as server_info: schema = Schema(api, server_info, client)
schema = Schema(api, server_info, client) object.__setattr__(api, '_schema', schema)
object.__setattr__(api, '_schema', schema)
fingerprint = str(schema['fingerprint']) fingerprint = str(schema['fingerprint'])
package_name = '{}${}'.format(__name__, fingerprint) package_name = '{}${}'.format(__name__, fingerprint)

View File

@ -21,6 +21,8 @@
This base platform module exports default filesystem paths. This base platform module exports default filesystem paths.
''' '''
import os
class BasePathNamespace(object): class BasePathNamespace(object):
BASH = "/bin/bash" BASH = "/bin/bash"
@ -353,4 +355,17 @@ class BasePathNamespace(object):
IPA_CUSTODIA_AUDIT_LOG = '/var/log/ipa-custodia.audit.log' IPA_CUSTODIA_AUDIT_LOG = '/var/log/ipa-custodia.audit.log'
IPA_GETKEYTAB = '/usr/sbin/ipa-getkeytab' IPA_GETKEYTAB = '/usr/sbin/ipa-getkeytab'
@property
def USER_CACHE_PATH(self):
return (
os.environ.get('XDG_CACHE_HOME') or
os.path.join(
os.environ.get(
'HOME',
os.path.expanduser('~')
),
'.cache'
)
)
path_namespace = BasePathNamespace path_namespace = BasePathNamespace