Merge pull request #27 from uggla/properinstall

Review of installation process
This commit is contained in:
Bruno Cornec 2016-03-04 17:01:30 +01:00
commit 1e9e02c15a
17 changed files with 456 additions and 105 deletions

2
.gitignore vendored

@ -41,6 +41,8 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
redfish-client/tests/Dockerfile
redfish-client/tests/python-redfish.src.tar.gz
# Translations
*.mo

@ -15,6 +15,7 @@ Contents:
installation
usage
develsetup
testing
classesdoc
contributing
help

@ -10,9 +10,11 @@ Use::
Pip will install :
1. The library and all dependencies into your site-packages directory
2. Redfish client master conf file into /etc/redfish_client.conf
3. Data file (templates) into /usr/share/redfish-client/templates
1. The library and all dependencies into prefix/lib/pythonX.Y/site-packages directory
2. Redfish client master conf file into prefix/etc/redfish_client.conf
Unless if prefix = '/usr', in that case force configuration file to be in /etc
3. Data file (templates) into prefix/share/redfish-client/templates
Point 2 and 3 above need root access to your system. If you don't have root
access on your system, please follow `Using pip and virtualenv`_ section.
@ -45,7 +47,7 @@ Using pip and virtualenv
4. Install using pip::
pip install python-virtualenv
pip install python-redfish
All files are installed under your virtualenv.
@ -55,7 +57,7 @@ Using the sources
#. Follow `get the sources <http://pythonhosted.org/python-redfish/readme.html#get-the-sources>`_ section to retrieve the sources.
#. Install from the source using::
python setup.py install
python setup.py install --prefix="/usr/local"
Using rpm package

25
doc/source/testing.rst Normal file

@ -0,0 +1,25 @@
=============
Running tests
=============
redfish module tests
--------------------
Tests are not functional for the redfish module yet.
refish-client tests
-------------------
#. Create your development environment following `Developer setup <develsetup.html>`_.
#. Install docker using the `procedure <https://docs.docker.com/engine/installation/>`_.
#. Ensure you can use docker with your current user.
#. Jump into redfish-python directory containing the sources.
#. Install required modules for testings::
pip install -t test-requirements.txt
#. Run the test::
py.test redfish-client

@ -1,2 +1,2 @@
[redfish-client]
templates_path = /usr/share/redfish-client/templates
templates_path = PBTEMPLATEPATH

@ -17,14 +17,15 @@ redfish-client ::
Options:
-h --help Show this screen.
--version Show version.
--conf_file FILE Configuration file [default: ~/.redfish.conf]
--insecure Ignore SSL certificates
--debug LEVEL Run in debug mode, LEVEL from 1 to 3 increase verbosity
Security warning LEVEL > 1 could reveal password into the logs
--debugfile FILE Specify the client debugfile [default: redfish-client.log]
--libdebugfile FILE Specify python-redfish library log file [default: /var/log/python-redfish/python-redfish.log]
-h --help Show this screen.
--version Show version.
-c --config FILE Configuration file
-i --inventory FILE Configuration file [default: $HOME/.redfish.conf]
--insecure Ignore SSL certificates
--debug LEVEL Run in debug mode, LEVEL from 1 to 3 increase verbosity
Security warning LEVEL > 1 could reveal password into the logs
--debugfile FILE Specify the client debugfile [default: redfish-client.log]
--libdebugfile FILE Specify python-redfish library log file [default: /var/log/python-redfish/python-redfish.log]
config commands : manage the configuration file.
manager commands : manage the manager (Ligh out management). If <manager_name>
@ -42,24 +43,25 @@ import jinja2
import requests.packages.urllib3
import redfish
class ConfigFile(object):
'''redfisht-client configuration file management'''
def __init__(self, config_file):
'''Initialize the configuration file
class InventoryFile(object):
'''redfisht-client inventory file management'''
def __init__(self, inventory_file):
'''Initialize the inventory file
Open and load configuration file data.
If the file does not exist create an empty one ready to receive data
:param config_file: File name of the configuration file
:param inventory_file: File name of the configuration file
default: ~/.redfish.conf
:type config-file: str
:returns: Nothing
'''
self._config_file = config_file
self._inventory_file = inventory_file
# read json file
try:
with open(self._config_file) as json_data:
with open(self._inventory_file) as json_data:
self.data = json.load(json_data)
json_data.close()
except (ValueError, IOError):
@ -68,7 +70,7 @@ class ConfigFile(object):
def save(self):
'''Save the configuration file data'''
try:
with open(self._config_file, 'w') as json_data:
with open(self._inventory_file, 'w') as json_data:
json.dump(self.data, json_data)
json_data.close()
except IOError as e:
@ -141,7 +143,8 @@ class ConfigFile(object):
self.manager_incorect(e)
elif parameter == 'password':
try:
self.data['Managers'][manager_name]['password'] = parameter_value
self.data['Managers'][manager_name]['password'] \
= parameter_value
except KeyError as e:
self.manager_incorect(e)
elif parameter == 'manager_name':
@ -209,7 +212,6 @@ class RedfishClientException(Exception):
if __name__ == '__main__':
'''Main application redfish-client'''
# Functions
def show_manager(all=False):
'''Display manager info
@ -219,16 +221,19 @@ if __name__ == '__main__':
'''
print('Managers configured :')
for manager in conf_file.get_managers():
print(manager)
if all is True:
info = conf_file.get_manager_info(manager)
print('\tUrl : {}'.format(info['url']))
print('\tLogin : {}'.format(info['login']))
print('\tPassword : {}'.format(info['password']))
if(not inventory.get_managers()):
print("None")
else:
for manager in inventory.get_managers():
print(manager)
if all is True:
info = inventory.get_manager_info(manager)
print('\tUrl : {}'.format(info['url']))
print('\tLogin : {}'.format(info['login']))
print('\tPassword : {}'.format(info['password']))
def get_manager_info(manager_name, check_SSL):
connection_parameters = conf_file.get_manager_info(manager_name)
connection_parameters = inventory.get_manager_info(manager_name)
if not connection_parameters['login']:
simulator = True
enforceSSL = False
@ -252,18 +257,21 @@ if __name__ == '__main__':
sys.stderr.write(str(e.advices))
sys.exit(1)
# Display manager information using jinja2 template
# Display manager information using jinja2 template
try:
template = jinja2_env.get_template("manager_info.template")
except jinja2.exceptions.TemplateNotFound as e:
print('Template "{}" not found in {}.'.format(e.message, jinja2_env.loader.searchpath[0]))
logger.debug('Template "%s" not found in %s.' % (e.message, jinja2_env.loader.searchpath[0]))
print('Template "{}" not found in {}.'
.format(e.message, jinja2_env.loader.searchpath[0]))
logger.debug('Template "%s" not found in %s.'
% (e.message, jinja2_env.loader.searchpath[0]))
sys.exit(1)
print template.render(r=remote_mgmt)
#################################################################
# Main program
#################################################################
redfishclient_version = "redfish-client PBVER"
# Parse and manage arguments
@ -320,47 +328,43 @@ if __name__ == '__main__':
logger.info("Arguments parsed")
logger.debug(arguments)
# Get $HOME and $VIRTUAL_ENV environment variables.
# Get $HOME environment variables.
HOME = os.getenv('HOME')
VIRTUAL_ENV = os.getenv('VIRTUAL_ENV')
if not HOME:
if(not HOME):
print('$HOME environment variable not set, please check your system')
logger.error('$HOME environment variable not set')
sys.exit(1)
logger.debug("Home directory : %s" % HOME)
if VIRTUAL_ENV:
logger.debug("Virtual env : %s" % VIRTUAL_ENV)
# Load master conf file
config = ConfigParser.ConfigParser(allow_no_value=True)
logger.debug("Read master configuration file")
master_conf_file_path = "/etc/redfish-client.conf"
if VIRTUAL_ENV:
logger.debug("Read master configuration file from virtual environment")
master_conf_file_path = VIRTUAL_ENV + master_conf_file_path
if not os.path.isfile(master_conf_file_path):
print('Master configuration file not found at {}.'.format(master_conf_file_path))
logger.error('Master configuration file not found at %s.' % master_conf_file_path)
sys.exit(1)
config.read(master_conf_file_path)
arguments['--conf_file'] = arguments['--conf_file'].replace('~', HOME)
conf_file = ConfigFile(arguments['--conf_file'])
# Load config
config = ConfigParser.ConfigParser(allow_no_value=True)
logger.debug("Read configuration file")
configfile = 'PBCONFFILE'
if(arguments['--config']):
configfile = [arguments['--config']]
logger.debug("Overwrite configuration specified by user at %s"
% configfile)
if(os.path.isfile(configfile)):
logger.debug('Configuration found at %s.' % configfile)
config.read(configfile)
else:
print('Configuration file not found at {}.'.format(configfile))
logger.error('Configuration file not found at %s.' % configfile)
sys.exit(1)
arguments['--inventory'] = arguments['--inventory'].replace('~', HOME)
arguments['--inventory'] = arguments['--inventory'].replace('$HOME', HOME)
inventory = InventoryFile(arguments['--inventory'])
# Initialize Template system (jinja2)
# TODO : set the template file location into cmd line default to /usr/share/python-redfish/templates ?
templates_path = config.get("redfish-client", "templates_path")
logger.debug("Initialize template system")
if VIRTUAL_ENV:
logger.debug("Read templates file from virtual environment")
templates_path = VIRTUAL_ENV + templates_path
jinja2_env = jinja2.Environment(loader=jinja2.FileSystemLoader(templates_path))
jinja2_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(templates_path))
# Check cmd line parameters
if arguments['config'] is True:
logger.debug("Config commands")
@ -372,37 +376,37 @@ if __name__ == '__main__':
show_manager(True)
elif arguments['add'] is True:
logger.debug('add command')
conf_file.add_manager(arguments['<manager_name>'],
inventory.add_manager(arguments['<manager_name>'],
arguments['<manager_url>'],
arguments['<login>'],
arguments['<password>'])
logger.debug(conf_file.data)
conf_file.save()
logger.debug(inventory.data)
inventory.save()
elif arguments['del'] is True:
logger.debug('del command')
conf_file.delete_manager(arguments['<manager_name>'])
logger.debug(conf_file.data)
conf_file.save()
inventory.delete_manager(arguments['<manager_name>'])
logger.debug(inventory.data)
inventory.save()
elif arguments['modify'] is True:
logger.debug('modify command')
if arguments['url'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
inventory.modify_manager(arguments['<manager_name>'],
'url',
arguments['<changed_value>'])
elif arguments['login'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
inventory.modify_manager(arguments['<manager_name>'],
'login',
arguments['<changed_value>'])
elif arguments['password'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
inventory.modify_manager(arguments['<manager_name>'],
'password',
arguments['<changed_value>'])
elif arguments['manager_name'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
inventory.modify_manager(arguments['<manager_name>'],
'manager_name',
arguments['<changed_value>'])
logger.debug(conf_file.data)
conf_file.save()
logger.debug(inventory.data)
inventory.save()
if arguments['manager'] is True:
logger.debug("Manager commands")
if arguments['getinfo'] is True:
@ -413,7 +417,7 @@ if __name__ == '__main__':
else:
manager_name = arguments['<manager_name>']
# Check if the default section is available in our conf file
conf_file.check_manager(manager_name)
inventory.check_manager(manager_name)
if arguments['--insecure'] is True:
get_manager_info(manager_name, False)
else:

@ -1 +1 @@
redfish-client.py
redfish-client

@ -0,0 +1,13 @@
FROM debian:jessie
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && \
apt-get install -y apt-utils && \
apt-get install -y python-pip
COPY python-redfish.src.tar.gz /python-redfish.src.tar.gz
RUN mkdir /var/log/python-redfish
RUN tar xvvf python-redfish.src.tar.gz
RUN cd python-redfish* && \
pip install -r requirements.txt && \
python setup.py install
CMD ["/bin/bash"]

@ -0,0 +1,11 @@
FROM fedora:23
RUN dnf install -y python-pip && \
dnf install -y git && \
dnf install -y tar
COPY python-redfish.src.tar.gz /python-redfish.src.tar.gz
RUN mkdir /var/log/python-redfish
RUN tar xvvf python-redfish.src.tar.gz
RUN cd python-redfish* && \
pip install -r requirements.txt && \
python setup.py install
CMD ["/bin/bash"]

@ -0,0 +1,7 @@
FROM fedora:23
RUN dnf install -y python-pip && \
dnf install -y git
RUN mkdir /var/log/python-redfish
RUN pip install python-redfish
CMD ["/bin/bash"]

@ -0,0 +1,13 @@
FROM ubuntu:wily
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && \
apt-get install -y apt-utils && \
apt-get install -y python-pip
COPY python-redfish.src.tar.gz /python-redfish.src.tar.gz
RUN mkdir /var/log/python-redfish
RUN tar xvvf python-redfish.src.tar.gz
RUN cd python-redfish* && \
pip install -r requirements.txt && \
python setup.py install
CMD ["/bin/bash"]

@ -0,0 +1,89 @@
# coding=utf-8
import os
import stat
import subprocess
import re
from docker import Client
from path import Path
class DockerTest():
def __init__(self):
self.cli = Client(base_url='unix://var/run/docker.sock')
def build(self, dockerfile):
dockerfile = Path(dockerfile)
tag = 'rf' + dockerfile.basename().replace('Dockerfile.', '')
dockerfile.copy('redfish-client/tests/Dockerfile')
response = [line for line in self.cli.build(
path='redfish-client/tests',
tag=tag,
rm=True)]
return(response)
def run(self, image, command):
container = self.cli.create_container(image=image,
command=command,
tty=True,
stdin_open=True)
self.cli.start(container=container.get('Id'))
self.cli.wait(container=container.get('Id'))
response = self.cli.logs(container=container.get('Id'),
stdout=True)
return(response)
def test_dockersocket():
mode = os.stat('/var/run/docker.sock').st_mode
isSocket = stat.S_ISSOCK(mode)
assert isSocket, 'Make sure docker services are running'
def test_docker():
cli = Client(base_url='unix://var/run/docker.sock')
response = cli.containers()
assert isinstance(response, list), 'Ensure you have sufficiant' + \
'credentials to use docker with' + \
'your current user'
def test_sources():
output = subprocess.check_output(["python", "setup.py", "sdist"])
search = re.search(r"removing '(\S+)'", output)
filename = Path('dist/' + search.group(1) + '.tar.gz')
filename.copy('redfish-client/tests/python-redfish.src.tar.gz')
assert Path('redfish-client/tests/python-redfish.src.tar.gz').isfile()
def test_dockerbuild():
docker = DockerTest()
dockerfiles = ('redfish-client/tests/Dockerfile.ubuntu',
'redfish-client/tests/Dockerfile.debian',
'redfish-client/tests/Dockerfile.fedora',
'redfish-client/tests/Dockerfile.fedorapip')
for dockerfile in dockerfiles:
print('Testing : {}'.format(dockerfile))
response = docker.build(dockerfile)
status = response.pop()
assert 'Successfully built' in status
def test_install():
docker = DockerTest()
images = ('rfubuntu', 'rfdebian', 'rffedora', 'rffedorapip')
for img in images:
print('Testing : {}'.format(img))
response = docker.run(img, 'redfish-client config showall')
print(response)
assert ('Managers configured' in response and 'None' in response)
def test_versionformat():
docker = DockerTest()
images = ('rfubuntu', 'rfdebian', 'rffedora', 'rffedorapip')
for img in images:
print('Testing : {}'.format(img))
response = docker.run(img, 'redfish-client --version')
print(response)
assert (re.match('redfish-client \d+\.\d+', response))

@ -18,7 +18,7 @@ from redfish.main import *
#import redfish.types
try:
__version__ = pbr.version.VersionInfo('redfish').version_string()
__version__ = pbr.version.VersionInfo('redfish').release_string()
except Exception, e:
if "Versioning for this project requires either an sdist tarball" in e.message:
pass

@ -2,9 +2,7 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=0.6,!=0.7,<1.0
#oslo.log>=1.0,<2.0
Babel>=1.3
pbr>=0.8
tortilla>=0.4.1
Jinja2>=2.7.3
Sphinx>=1.2.3

@ -27,10 +27,9 @@ packages =
scripts =
redfish-client/redfish-client
data_files =
usr/share/redfish-client/templates = redfish-client/templates/*
etc/ = redfish-client/etc/*
[data_files_helper]
conf = 'redfish-client/etc/redfish-client.conf', 'etc'
templates = 'redfish-client/templates/*', 'share/redfish-client/templates'
[build_sphinx]
source-dir = doc/source

203
setup.py

@ -1,21 +1,28 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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,
# 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.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import os
import sys
import fileinput
import re
import pprint
import distutils
import ConfigParser
import setuptools
from setuptools import Distribution
from setuptools.command.install import install
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
@ -25,6 +32,192 @@ try:
except ImportError:
pass
class OnlyGetScriptPath(install):
'''Extend setuptools install class and replace run method to go through
setuptool installation and retrieve the script path
'''
def run(self):
# does not call install.run() by design
self.distribution.install_scripts = self.install_scripts
class DataFilesHelper():
'''Class to help manage data files'''
def __init__(self):
'''Read setup.cfg and build the required data'''
self.data = {}
self.setupstruc = []
config = ConfigParser.ConfigParser()
config.read('setup.cfg')
for datafile in config.options('data_files_helper'):
src, dst = config.get('data_files_helper', datafile).split(',')
src = self.refinesrc(src)
dst = self.refinedst(dst)
self.data[datafile] = {'src': src,
'dst': dst,
'fdst': self.calculatedst(src, dst)}
self.update_setupstruc(src, dst)
try:
# Create an entry for scripts if available
src = config.get('files', 'scripts').split('\n')
src = [self.refinesrc(file)[0] for file in src if file]
self.data['script'] = {'src': src,
'dst': 'bin',
'fdst': self.calculatedst(src, 'bin')}
except ConfigParser.NoOptionError:
pass
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(self.data)
def trim(self, string):
string = string.strip()
string = string.strip("'")
string = string.strip('"')
return(string)
def refinesrc(self, file):
'''Refine source:
Expend source file if needed
:param file: source files
:type file: string
:returns: source files refined
:rtype: list
'''
file = self.trim(file)
if(file.endswith('/*')):
return(self.getfiles(file.replace('/*', '')))
else:
return([file])
def refinedst(self, file):
'''Refine destination:
Check if destination needs an exception
:param file: destination
:type path: string
:returns: destination refined
:rtype: string
'''
file = self.trim(file)
if('etc' in file and self.getprefix() == '/usr'):
return('/etc')
else:
return(file)
def calculatedst(self, src, dst):
'''Calculate the full destination path accordind to source and
destination
:param src: source files
:type path: list
:param dst: destination path
:type path: string
:returns: files with full destination
:rtype: list
'''
destination = []
for file in src:
if(dst.startswith('/')):
destination.append(os.path.join(dst,
os.path.basename(file)))
else:
destination.append(os.path.join(self.getprefix(),
dst,
os.path.basename(file)))
return(destination)
def getfiles(self, path):
'''Retrieve file list within a directory
:param path: directory path
:type path: string
:returns: file list
:rtype: list
'''
for root, dirs, files in os.walk(path):
file_list = [os.path.join(root, file) for file in files]
return(file_list)
def getprefix(self):
'''Retrieve setup tool calculated prefix
:returns: prefix
:rtype: string
'''
dist = Distribution({'cmdclass': {'install': OnlyGetScriptPath}})
dist.dry_run = True # not sure if necessary, but to be safe
dist.parse_config_files()
try:
dist.parse_command_line()
except (distutils.errors.DistutilsArgError, AttributeError):
pass
command = dist.get_command_obj('install')
command.ensure_finalized()
command.run()
prefix = dist.install_scripts.replace('/bin', '')
return prefix
def update_setupstruc(self, src, dst):
'''Create/update structure for setuptools.setup()
like the following example.
[('etc/', ['redfish-client/etc/redfish-client.conf']),
('share/redfish-client/templates',
['redfish-client/templates/manager_info.template',
'redfish-client/templates/bla.template'])]
'''
self.setupstruc.append((dst, src))
def getsetupstruc(self):
'''Retrieve setup structure compatible with setuptools.setup()
This is only to encapsulatate setupstruc property
:returns: datafiles source and destination
:rtype: setuptools structure
'''
return(self.setupstruc)
##########################################
# Functions
##########################################
def replaceAll(file, searchExp, replaceExp):
for line in fileinput.input(file, inplace=1):
if searchExp in line:
line = line.replace(searchExp, replaceExp)
sys.stdout.write(line)
def getversion():
with open("python_redfish.egg-info/PKG-INFO", "r") as f:
output = f.read()
s = re.search(r'\nVersion:\s+(\S+)', output)
return(s.group(1))
##########################################
# START
##########################################
datafiles = DataFilesHelper()
# Install software
setuptools.setup(
setup_requires=['pbr'],
pbr=True)
pbr=True,
data_files=datafiles.getsetupstruc())
if('install' in sys.argv):
# Update conf files
for file in datafiles.data['conf']['fdst']:
print('Update : {}'.format(file))
replaceAll(file, 'PBTEMPLATEPATH',
os.path.dirname(datafiles.data['templates']['fdst'][0]))
# Update script files
for file in datafiles.data['script']['fdst']:
print('Update : {}'.format(file))
replaceAll(file, 'PBCONFFILE', datafiles.data['conf']['fdst'][0])
replaceAll(file, 'PBVER', getversion())

@ -2,14 +2,8 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking<0.11,>=0.10.0
pytest>=2.6.4
coverage>=3.6
discover
python-subunit>=0.0.18
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
#oslosphinx>=2.2.0 # Apache-2.0
#oslotest>=1.2.0 # Apache-2.0
testrepository>=0.0.18
testscenarios>=0.4
testtools>=0.9.36,!=1.2.0
mock>=1.0.1
docker-py>=1.1.0
path.py>=5.2