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
#
import collections
import errno
import json
import os
from . import compat
from . import schema
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):
if api.env.in_tree:
from ipaserver import plugins
else:
client = rpcclient(api)
client.finalize()
try:
plugins = schema.get_package(api, client)
except schema.NotAvailable:
plugins = compat.get_package(api, client)
finally:
if client.isconnected():
client.disconnect()
with ServerInfo(api) as server_info:
client = rpcclient(api)
client.finalize()
try:
plugins = schema.get_package(api, server_info, client)
except schema.NotAvailable:
plugins = compat.get_package(api, server_info, client)
finally:
if client.isconnected():
client.disconnect()
return plugins

View File

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

View File

@ -19,6 +19,7 @@ from ipalib import errors, parameters, plugable
from ipalib.frontend import Object
from ipalib.output import Output
from ipalib.parameters import DefaultFrom, Flag, Password, Str
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipapython.dnsutil import DNSName
from ipapython.ipa_log_manager import log_mgr
@ -55,17 +56,6 @@ _PARAMS = {
'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__)
@ -359,62 +349,6 @@ class NotAvailable(Exception):
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):
"""
Store and provide schema for commands and topics
@ -436,7 +370,7 @@ class Schema(object):
"""
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):
self._dict = {}
@ -618,13 +552,12 @@ class Schema(object):
self._dict[ns][member].update(self._help[ns][member])
def get_package(api, client):
def get_package(api, server_info, client):
try:
schema = api._schema
except AttributeError:
with ServerInfo(api.env.hostname) as server_info:
schema = Schema(api, server_info, client)
object.__setattr__(api, '_schema', schema)
schema = Schema(api, server_info, client)
object.__setattr__(api, '_schema', schema)
fingerprint = str(schema['fingerprint'])
package_name = '{}${}'.format(__name__, fingerprint)

View File

@ -21,6 +21,8 @@
This base platform module exports default filesystem paths.
'''
import os
class BasePathNamespace(object):
BASH = "/bin/bash"
@ -353,4 +355,17 @@ class BasePathNamespace(object):
IPA_CUSTODIA_AUDIT_LOG = '/var/log/ipa-custodia.audit.log'
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