Reorganize into package and add tox for testing
Moves the functional code into an openstack_virtual_baremetal env and adds a tox configuration for testing. Existing unit tests for deploy.py are moved into the tests subpackage. Further unit tests for the other modules will be added in followup commits. Symlinks from the bin directory are left so the previous workflow should continue to work as before.
This commit is contained in:
parent
701a42c804
commit
9fe31995c5
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,3 +1,10 @@
|
||||
nodes.json
|
||||
env.yaml
|
||||
bmc_bm_pairs
|
||||
*.pyc
|
||||
*.pyo
|
||||
.coverage
|
||||
cover
|
||||
.testrepository
|
||||
.tox
|
||||
*.egg-info
|
||||
|
4
.testr.conf
Normal file
4
.testr.conf
Normal file
@ -0,0 +1,4 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 OS_LOG_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./openstack_virtual_baremetal ./openstack_virtual_baremetal $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
@ -1,141 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015 Red Hat Inc.
|
||||
#
|
||||
# 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 argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from neutronclient.v2_0 import client as neutronclient
|
||||
from novaclient import client as novaclient
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='build-nodes-json.py',
|
||||
description='Tool for collecting virtual IPMI details',
|
||||
)
|
||||
parser.add_argument('--env',
|
||||
dest='env',
|
||||
default=None,
|
||||
help='YAML file containing OVB environment details')
|
||||
parser.add_argument('--bmc_prefix',
|
||||
dest='bmc_prefix',
|
||||
default='bmc',
|
||||
help='BMC name prefix')
|
||||
parser.add_argument('--baremetal_prefix',
|
||||
dest='baremetal_prefix',
|
||||
default='baremetal',
|
||||
help='Baremetal name prefix')
|
||||
parser.add_argument('--private_net',
|
||||
dest='private_net',
|
||||
default='private',
|
||||
help='Private network name')
|
||||
parser.add_argument('--provision_net',
|
||||
dest='provision_net',
|
||||
default='provision',
|
||||
help='Provisioning network name')
|
||||
parser.add_argument('--nodes_json',
|
||||
dest='nodes_json',
|
||||
default='nodes.json',
|
||||
help='Destination to store the nodes json file to')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.env is None:
|
||||
bmc_base = args.bmc_prefix
|
||||
baremetal_base = args.baremetal_prefix
|
||||
private_net = args.private_net
|
||||
provision_net = args.provision_net
|
||||
else:
|
||||
with open(args.env) as f:
|
||||
e = yaml.safe_load(f)
|
||||
bmc_base = e['parameters']['bmc_prefix']
|
||||
baremetal_base = e['parameters']['baremetal_prefix']
|
||||
private_net = e['parameters']['private_net']
|
||||
provision_net = e['parameters']['provision_net']
|
||||
|
||||
cloud = os.environ.get('OS_CLOUD')
|
||||
if cloud:
|
||||
import os_client_config
|
||||
nova = os_client_config.make_client('compute', cloud=cloud)
|
||||
neutron = os_client_config.make_client('network', cloud=cloud)
|
||||
|
||||
else:
|
||||
username = os.environ.get('OS_USERNAME')
|
||||
password = os.environ.get('OS_PASSWORD')
|
||||
tenant = os.environ.get('OS_TENANT_NAME')
|
||||
auth_url = os.environ.get('OS_AUTH_URL')
|
||||
if not username or not password or not tenant or not auth_url:
|
||||
print('Source an appropriate rc file first')
|
||||
sys.exit(1)
|
||||
|
||||
nova = novaclient.Client(2, username, password, tenant, auth_url)
|
||||
neutron = neutronclient.Client(
|
||||
username=username,
|
||||
password=password,
|
||||
tenant_name=tenant,
|
||||
auth_url=auth_url
|
||||
)
|
||||
node_template = {
|
||||
'pm_type': 'pxe_ipmitool',
|
||||
'mac': '',
|
||||
'cpu': '',
|
||||
'memory': '',
|
||||
'disk': '',
|
||||
'arch': 'x86_64',
|
||||
'pm_user': 'admin',
|
||||
'pm_password': 'password',
|
||||
'pm_addr': '',
|
||||
'capabilities': 'boot_option:local',
|
||||
}
|
||||
|
||||
all_ports = sorted(neutron.list_ports()['ports'], key=lambda x: x['name'])
|
||||
bmc_ports = list([p for p in all_ports
|
||||
if p['name'].startswith(bmc_base)])
|
||||
bm_ports = list([p for p in all_ports
|
||||
if p['name'].startswith(baremetal_base)])
|
||||
if len(bmc_ports) != len(bm_ports):
|
||||
raise RuntimeError('Found different numbers of baremetal and '
|
||||
'bmc ports.')
|
||||
nodes = []
|
||||
bmc_bm_pairs = []
|
||||
|
||||
for bmc_port, baremetal_port in zip(bmc_ports, bm_ports):
|
||||
baremetal = nova.servers.get(baremetal_port['device_id'])
|
||||
node = dict(node_template)
|
||||
node['pm_addr'] = bmc_port['fixed_ips'][0]['ip_address']
|
||||
bmc_bm_pairs.append((node['pm_addr'], baremetal.name))
|
||||
node['mac'] = [baremetal.addresses[provision_net][0]['OS-EXT-IPS-MAC:mac_addr']]
|
||||
flavor = nova.flavors.get(baremetal.flavor['id'])
|
||||
node['cpu'] = flavor.vcpus
|
||||
node['memory'] = flavor.ram
|
||||
node['disk'] = flavor.disk
|
||||
nodes.append(node)
|
||||
|
||||
with open(args.nodes_json, 'w') as node_file:
|
||||
contents = json.dumps({'nodes': nodes}, indent=2)
|
||||
node_file.write(contents)
|
||||
print(contents)
|
||||
|
||||
with open('bmc_bm_pairs', 'w') as pairs_file:
|
||||
pairs_file.write('# A list of BMC addresses and the name of the '
|
||||
'instance that BMC manages.\n')
|
||||
for i in bmc_bm_pairs:
|
||||
pair = '%s %s' % i
|
||||
pairs_file.write(pair + '\n')
|
||||
print(pair)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1
bin/build-nodes-json
Symbolic link
1
bin/build-nodes-json
Symbolic link
@ -0,0 +1 @@
|
||||
../openstack_virtual_baremetal/build_nodes_json.py
|
156
bin/deploy.py
156
bin/deploy.py
@ -1,156 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2016 Red Hat Inc.
|
||||
#
|
||||
# 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 argparse
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from heatclient import client as heat_client
|
||||
from heatclient.common import template_utils
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
|
||||
def _parse_args():
|
||||
parser = argparse.ArgumentParser(description='Deploy an OVB environment')
|
||||
parser.add_argument('--env',
|
||||
help='Path to Heat environment file describing the OVB '
|
||||
'environment to be deployed. Default: %(default)s',
|
||||
default='env.yaml')
|
||||
parser.add_argument('--id',
|
||||
help='Identifier to add to all resource names. The '
|
||||
'resulting names will look like undercloud-ID or '
|
||||
'baremetal-ID. By default no changes will be made to '
|
||||
'the resource names. If an id is specified, a new '
|
||||
'environment file will be written to env-ID.yaml. ')
|
||||
parser.add_argument('--name',
|
||||
help='Name for the Heat stack to be created. Defaults '
|
||||
'to "baremetal" in a standard deployment. If '
|
||||
'--quintupleo is specified then the default is '
|
||||
'"quintupleo".')
|
||||
parser.add_argument('--quintupleo',
|
||||
help='Deploy a full environment suitable for TripleO '
|
||||
'development.',
|
||||
action='store_true',
|
||||
default=False)
|
||||
return parser.parse_args()
|
||||
|
||||
def _process_args(args):
|
||||
if args.id and not args.quintupleo:
|
||||
raise RuntimeError('--id requires --quintupleo')
|
||||
|
||||
env_path = args.env
|
||||
if args.name:
|
||||
stack_name = args.name
|
||||
else:
|
||||
stack_name = 'baremetal'
|
||||
if args.quintupleo:
|
||||
stack_name = 'quintupleo'
|
||||
if not args.quintupleo:
|
||||
stack_template = 'templates/virtual-baremetal.yaml'
|
||||
else:
|
||||
stack_template = 'templates/quintupleo.yaml'
|
||||
return stack_name, stack_template
|
||||
|
||||
def _add_identifier(env_data, name, identifier, default=None, parameter=True):
|
||||
param_key = 'parameters'
|
||||
if not parameter:
|
||||
param_key = 'parameter_defaults'
|
||||
if param_key not in env_data or not env_data[param_key]:
|
||||
env_data[param_key] = {}
|
||||
original = env_data[param_key].get(name)
|
||||
if original is None:
|
||||
original = default
|
||||
if original is None:
|
||||
raise RuntimeError('No base value found when adding id')
|
||||
env_data[param_key][name] = '%s-%s' % (original, identifier)
|
||||
|
||||
def _generate_id_env(args):
|
||||
with open(args.env) as f:
|
||||
env_data = yaml.safe_load(f)
|
||||
_add_identifier(env_data, 'provision_net', args.id, default='provision')
|
||||
_add_identifier(env_data, 'public_net', args.id, default='public')
|
||||
_add_identifier(env_data, 'baremetal_prefix', args.id, default='baremetal')
|
||||
_add_identifier(env_data, 'bmc_prefix', args.id, default='bmc')
|
||||
_add_identifier(env_data, 'undercloud_name', args.id, default='undercloud')
|
||||
_add_identifier(env_data, 'overcloud_internal_net', args.id,
|
||||
default='internal', parameter=False)
|
||||
_add_identifier(env_data, 'overcloud_storage_net', args.id,
|
||||
default='storage', parameter=False)
|
||||
_add_identifier(env_data, 'overcloud_storage_mgmt_net', args.id,
|
||||
default='storage_mgmt', parameter=False)
|
||||
_add_identifier(env_data, 'overcloud_tenant_net', args.id,
|
||||
default='tenant', parameter=False)
|
||||
env_path = 'env-%s.yaml' % args.id
|
||||
with open(env_path, 'w') as f:
|
||||
yaml.safe_dump(env_data, f, default_flow_style=False)
|
||||
return env_path
|
||||
|
||||
def _get_heat_client():
|
||||
cloud = os.environ.get('OS_CLOUD')
|
||||
if cloud:
|
||||
import os_client_config
|
||||
return os_client_config.make_client('orchestration', cloud=cloud)
|
||||
else:
|
||||
username = os.environ.get('OS_USERNAME')
|
||||
password = os.environ.get('OS_PASSWORD')
|
||||
tenant = os.environ.get('OS_TENANT_NAME')
|
||||
auth_url = os.environ.get('OS_AUTH_URL')
|
||||
if not username or not password or not tenant or not auth_url:
|
||||
print('Source an appropriate rc file first')
|
||||
sys.exit(1)
|
||||
|
||||
# Get token for Heat to use
|
||||
kclient = keystone_client.Client(username=username, password=password,
|
||||
tenant_name=tenant, auth_url=auth_url)
|
||||
token_data = kclient.get_raw_token_from_identity_service(
|
||||
username=username,
|
||||
password=password,
|
||||
tenant_name=tenant,
|
||||
auth_url=auth_url)
|
||||
token_id = token_data['token']['id']
|
||||
# Get Heat endpoint
|
||||
for endpoint in token_data['serviceCatalog']:
|
||||
if endpoint['name'] == 'heat':
|
||||
# TODO: What if there's more than one endpoint?
|
||||
heat_endpoint = endpoint['endpoints'][0]['publicURL']
|
||||
|
||||
return heat_client.Client('1', endpoint=heat_endpoint, token=token_id)
|
||||
|
||||
def _deploy(stack_name, stack_template, env_path):
|
||||
hclient = _get_heat_client()
|
||||
|
||||
template_files, template = template_utils.get_template_contents(
|
||||
stack_template)
|
||||
env_files, env = template_utils.process_multiple_environments_and_files(
|
||||
['templates/resource-registry.yaml', env_path])
|
||||
all_files = {}
|
||||
all_files.update(template_files)
|
||||
all_files.update(env_files)
|
||||
|
||||
hclient.stacks.create(stack_name=stack_name,
|
||||
template=template,
|
||||
environment=env,
|
||||
files=all_files)
|
||||
|
||||
print 'Deployment of stack "%s" started.' % stack_name
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = _parse_args()
|
||||
env_path = args.env
|
||||
stack_name, stack_template = _process_args(args)
|
||||
if args.id:
|
||||
env_path = _generate_id_env(args)
|
||||
_deploy(stack_name, stack_template, env_path)
|
1
bin/deploy.py
Symbolic link
1
bin/deploy.py
Symbolic link
@ -0,0 +1 @@
|
||||
../openstack_virtual_baremetal/deploy.py
|
184
bin/openstackbmc
184
bin/openstackbmc
@ -1,184 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Virtual BMC for controlling OpenStack instances, based on fakebmc from
|
||||
# python-pyghmi
|
||||
|
||||
# Sample ipmitool commands:
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power on
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power status
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootdev pxe|disk
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 mc reset cold
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
|
||||
from novaclient import client as novaclient
|
||||
from novaclient import exceptions
|
||||
import pyghmi.ipmi.bmc as bmc
|
||||
|
||||
|
||||
class OpenStackBmc(bmc.Bmc):
|
||||
def __init__(self, authdata, port, address, instance, user, password, tenant,
|
||||
auth_url):
|
||||
super(OpenStackBmc, self).__init__(authdata, port=port, address=address)
|
||||
self.novaclient = novaclient.Client(2, user, password,
|
||||
tenant, auth_url)
|
||||
self.instance = None
|
||||
# At times the bmc service is started before important things like
|
||||
# networking have fully initialized. Keep trying to find the
|
||||
# instance indefinitely, since there's no point in continuing if
|
||||
# we don't have an instance.
|
||||
while True:
|
||||
try:
|
||||
self._find_instance(instance)
|
||||
if self.instance is not None:
|
||||
name = self.novaclient.servers.get(self.instance).name
|
||||
self.log('Managing instance: %s UUID: %s' %
|
||||
(name, self.instance))
|
||||
break
|
||||
except Exception as e:
|
||||
self.log('Exception finding instance "%s": %s' % (instance, e))
|
||||
time.sleep(1)
|
||||
|
||||
def _find_instance(self, instance):
|
||||
try:
|
||||
self.novaclient.servers.get(instance)
|
||||
self.instance = instance
|
||||
except exceptions.NotFound:
|
||||
name_regex = '^%s$' % instance
|
||||
i = self.novaclient.servers.list(search_opts={'name': name_regex})
|
||||
if len(i) > 1:
|
||||
self.log('Ambiguous instance name %s' % instance)
|
||||
sys.exit(1)
|
||||
try:
|
||||
self.instance = i[0].id
|
||||
except IndexError:
|
||||
self.log('Could not find specified instance %s' % instance)
|
||||
sys.exit(1)
|
||||
|
||||
def get_boot_device(self):
|
||||
server = self.novaclient.servers.get(self.instance)
|
||||
retval = 'network' if server.metadata.get('libvirt:pxe-first') else 'hd'
|
||||
self.log('Reporting boot device', retval)
|
||||
return retval
|
||||
|
||||
def set_boot_device(self, bootdevice):
|
||||
server = self.novaclient.servers.get(self.instance)
|
||||
if bootdevice == 'network':
|
||||
self.novaclient.servers.set_meta_item(server, 'libvirt:pxe-first', '1')
|
||||
else:
|
||||
self.novaclient.servers.set_meta_item(server, 'libvirt:pxe-first', '')
|
||||
self.log('Set boot device to', bootdevice)
|
||||
|
||||
def cold_reset(self):
|
||||
# Reset of the BMC, not managed system, here we will exit the demo
|
||||
self.log('Shutting down in response to BMC cold reset request')
|
||||
sys.exit(0)
|
||||
|
||||
def _instance_active(self):
|
||||
return self.novaclient.servers.get(self.instance).status == 'ACTIVE'
|
||||
|
||||
def get_power_state(self):
|
||||
self.log('Getting power state for %s' % self.instance)
|
||||
return self._instance_active()
|
||||
|
||||
def power_off(self):
|
||||
# this should be power down without waiting for clean shutdown
|
||||
if self._instance_active():
|
||||
try:
|
||||
self.novaclient.servers.stop(self.instance)
|
||||
self.log('Powered off %s' % self.instance)
|
||||
except exceptions.Conflict as e:
|
||||
# This can happen if we get two requests to start a server in
|
||||
# short succession. The instance may then be in a powering-on
|
||||
# state, which means it is invalid to start it again.
|
||||
self.log('Ignoring exception: "%s"' % e)
|
||||
else:
|
||||
self.log('%s is already off.' % self.instance)
|
||||
return 0xd5
|
||||
|
||||
def power_on(self):
|
||||
if not self._instance_active():
|
||||
try:
|
||||
self.novaclient.servers.start(self.instance)
|
||||
self.log('Powered on %s' % self.instance)
|
||||
except exceptions.Conflict as e:
|
||||
# This can happen if we get two requests to start a server in
|
||||
# short succession. The instance may then be in a powering-on
|
||||
# state, which means it is invalid to start it again.
|
||||
self.log('Ignoring exception: "%s"' % e)
|
||||
else:
|
||||
self.log('%s is already on.' % self.instance)
|
||||
return 0xd5
|
||||
|
||||
def power_reset(self):
|
||||
pass
|
||||
|
||||
def power_shutdown(self):
|
||||
# should attempt a clean shutdown
|
||||
self.novaclient.servers.stop(self.instance)
|
||||
self.log('Politely shut down %s' % self.instance)
|
||||
|
||||
def log(self, *msg):
|
||||
print(' '.join(msg))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='openstackbmc',
|
||||
description='Virtual BMC for controlling OpenStack instance',
|
||||
)
|
||||
parser.add_argument('--port',
|
||||
dest='port',
|
||||
type=int,
|
||||
default=623,
|
||||
help='Port to listen on; defaults to 623')
|
||||
parser.add_argument('--address',
|
||||
dest='address',
|
||||
default='::',
|
||||
help='Address to bind to; defaults to ::')
|
||||
parser.add_argument('--instance',
|
||||
dest='instance',
|
||||
required=True,
|
||||
help='The uuid or name of the OpenStack instance to manage')
|
||||
parser.add_argument('--os-user',
|
||||
dest='user',
|
||||
required=True,
|
||||
help='The user for connecting to OpenStack')
|
||||
parser.add_argument('--os-password',
|
||||
dest='password',
|
||||
required=True,
|
||||
help='The password for connecting to OpenStack')
|
||||
parser.add_argument('--os-tenant',
|
||||
dest='tenant',
|
||||
required=True,
|
||||
help='The tenant for connecting to OpenStack')
|
||||
parser.add_argument('--os-auth-url',
|
||||
dest='auth_url',
|
||||
required=True,
|
||||
help='The OpenStack Keystone auth url')
|
||||
args = parser.parse_args()
|
||||
mybmc = OpenStackBmc({'admin': 'password'}, port=args.port,
|
||||
address='::ffff:%s' % args.address,
|
||||
instance=args.instance,
|
||||
user=args.user,
|
||||
password=args.password,
|
||||
tenant=args.tenant,
|
||||
auth_url=args.auth_url)
|
||||
mybmc.listen()
|
1
bin/openstackbmc
Symbolic link
1
bin/openstackbmc
Symbolic link
@ -0,0 +1 @@
|
||||
../openstack_virtual_baremetal/openstackbmc.py
|
0
openstack_virtual_baremetal/__init__.py
Normal file
0
openstack_virtual_baremetal/__init__.py
Normal file
171
openstack_virtual_baremetal/build_nodes_json.py
Executable file
171
openstack_virtual_baremetal/build_nodes_json.py
Executable file
@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015 Red Hat Inc.
|
||||
#
|
||||
# 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 argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from neutronclient.v2_0 import client as neutronclient
|
||||
from novaclient import client as novaclient
|
||||
|
||||
|
||||
def _parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='build-nodes-json.py',
|
||||
description='Tool for collecting virtual IPMI details',
|
||||
)
|
||||
parser.add_argument('--env',
|
||||
dest='env',
|
||||
default=None,
|
||||
help='YAML file containing OVB environment details')
|
||||
parser.add_argument('--bmc_prefix',
|
||||
dest='bmc_prefix',
|
||||
default='bmc',
|
||||
help='BMC name prefix')
|
||||
parser.add_argument('--baremetal_prefix',
|
||||
dest='baremetal_prefix',
|
||||
default='baremetal',
|
||||
help='Baremetal name prefix')
|
||||
parser.add_argument('--private_net',
|
||||
dest='private_net',
|
||||
default='private',
|
||||
help='DEPRECATED: This parameter is ignored.')
|
||||
parser.add_argument('--provision_net',
|
||||
dest='provision_net',
|
||||
default='provision',
|
||||
help='Provisioning network name')
|
||||
parser.add_argument('--nodes_json',
|
||||
dest='nodes_json',
|
||||
default='nodes.json',
|
||||
help='Destination to store the nodes json file to')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def _get_names(args):
|
||||
if args.env is None:
|
||||
bmc_base = args.bmc_prefix
|
||||
baremetal_base = args.baremetal_prefix
|
||||
provision_net = args.provision_net
|
||||
else:
|
||||
with open(args.env) as f:
|
||||
e = yaml.safe_load(f)
|
||||
bmc_base = e['parameters']['bmc_prefix']
|
||||
baremetal_base = e['parameters']['baremetal_prefix']
|
||||
provision_net = e['parameters']['provision_net']
|
||||
return bmc_base, baremetal_base, provision_net
|
||||
|
||||
|
||||
def _get_clients():
|
||||
cloud = os.environ.get('OS_CLOUD')
|
||||
if cloud:
|
||||
import os_client_config
|
||||
nova = os_client_config.make_client('compute', cloud=cloud)
|
||||
neutron = os_client_config.make_client('network', cloud=cloud)
|
||||
|
||||
else:
|
||||
username = os.environ.get('OS_USERNAME')
|
||||
password = os.environ.get('OS_PASSWORD')
|
||||
tenant = os.environ.get('OS_TENANT_NAME')
|
||||
auth_url = os.environ.get('OS_AUTH_URL')
|
||||
if not username or not password or not tenant or not auth_url:
|
||||
print('Source an appropriate rc file first')
|
||||
sys.exit(1)
|
||||
|
||||
nova = novaclient.Client(2, username, password, tenant, auth_url)
|
||||
neutron = neutronclient.Client(
|
||||
username=username,
|
||||
password=password,
|
||||
tenant_name=tenant,
|
||||
auth_url=auth_url
|
||||
)
|
||||
return nova, neutron
|
||||
|
||||
|
||||
def _get_ports(neutron, bmc_base, baremetal_base):
|
||||
all_ports = sorted(neutron.list_ports()['ports'], key=lambda x: x['name'])
|
||||
bmc_ports = list([p for p in all_ports
|
||||
if p['name'].startswith(bmc_base)])
|
||||
bm_ports = list([p for p in all_ports
|
||||
if p['name'].startswith(baremetal_base)])
|
||||
if len(bmc_ports) != len(bm_ports):
|
||||
raise RuntimeError('Found different numbers of baremetal and '
|
||||
'bmc ports.')
|
||||
return bmc_ports, bm_ports
|
||||
|
||||
|
||||
def _build_nodes(nova, bmc_ports, bm_ports, provision_net):
|
||||
node_template = {
|
||||
'pm_type': 'pxe_ipmitool',
|
||||
'mac': '',
|
||||
'cpu': '',
|
||||
'memory': '',
|
||||
'disk': '',
|
||||
'arch': 'x86_64',
|
||||
'pm_user': 'admin',
|
||||
'pm_password': 'password',
|
||||
'pm_addr': '',
|
||||
'capabilities': 'boot_option:local',
|
||||
}
|
||||
|
||||
nodes = []
|
||||
bmc_bm_pairs = []
|
||||
|
||||
for bmc_port, baremetal_port in zip(bmc_ports, bm_ports):
|
||||
baremetal = nova.servers.get(baremetal_port['device_id'])
|
||||
node = dict(node_template)
|
||||
node['pm_addr'] = bmc_port['fixed_ips'][0]['ip_address']
|
||||
bmc_bm_pairs.append((node['pm_addr'], baremetal.name))
|
||||
node['mac'] = [baremetal.addresses[provision_net][0]['OS-EXT-IPS-MAC:mac_addr']]
|
||||
flavor = nova.flavors.get(baremetal.flavor['id'])
|
||||
node['cpu'] = flavor.vcpus
|
||||
node['memory'] = flavor.ram
|
||||
node['disk'] = flavor.disk
|
||||
nodes.append(node)
|
||||
return nodes, bmc_bm_pairs
|
||||
|
||||
|
||||
def _write_nodes(nodes, args):
|
||||
with open(args.nodes_json, 'w') as node_file:
|
||||
contents = json.dumps({'nodes': nodes}, indent=2)
|
||||
node_file.write(contents)
|
||||
print(contents)
|
||||
|
||||
|
||||
# TODO(bnemec): parameterize this based on args.nodes_json
|
||||
def _write_pairs(bmc_bm_pairs):
|
||||
with open('bmc_bm_pairs', 'w') as pairs_file:
|
||||
pairs_file.write('# A list of BMC addresses and the name of the '
|
||||
'instance that BMC manages.\n')
|
||||
for i in bmc_bm_pairs:
|
||||
pair = '%s %s' % i
|
||||
pairs_file.write(pair + '\n')
|
||||
print(pair)
|
||||
|
||||
|
||||
def main():
|
||||
args = _parse_args()
|
||||
bmc_base, baremetal_base, provision_net = _get_names(args)
|
||||
nova, neutron = _get_clients()
|
||||
bmc_ports, bm_ports = _get_ports(neutron, bmc_base, baremetal_base)
|
||||
nodes, bmc_bm_pairs = _build_nodes(nova, bmc_ports, bm_ports, provision_net)
|
||||
_write_nodes(nodes, args)
|
||||
_write_pairs(bmc_bm_pairs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
156
openstack_virtual_baremetal/deploy.py
Executable file
156
openstack_virtual_baremetal/deploy.py
Executable file
@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2016 Red Hat Inc.
|
||||
#
|
||||
# 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 argparse
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from heatclient import client as heat_client
|
||||
from heatclient.common import template_utils
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
|
||||
def _parse_args():
|
||||
parser = argparse.ArgumentParser(description='Deploy an OVB environment')
|
||||
parser.add_argument('--env',
|
||||
help='Path to Heat environment file describing the OVB '
|
||||
'environment to be deployed. Default: %(default)s',
|
||||
default='env.yaml')
|
||||
parser.add_argument('--id',
|
||||
help='Identifier to add to all resource names. The '
|
||||
'resulting names will look like undercloud-ID or '
|
||||
'baremetal-ID. By default no changes will be made to '
|
||||
'the resource names. If an id is specified, a new '
|
||||
'environment file will be written to env-ID.yaml. ')
|
||||
parser.add_argument('--name',
|
||||
help='Name for the Heat stack to be created. Defaults '
|
||||
'to "baremetal" in a standard deployment. If '
|
||||
'--quintupleo is specified then the default is '
|
||||
'"quintupleo".')
|
||||
parser.add_argument('--quintupleo',
|
||||
help='Deploy a full environment suitable for TripleO '
|
||||
'development.',
|
||||
action='store_true',
|
||||
default=False)
|
||||
return parser.parse_args()
|
||||
|
||||
def _process_args(args):
|
||||
if args.id and not args.quintupleo:
|
||||
raise RuntimeError('--id requires --quintupleo')
|
||||
|
||||
env_path = args.env
|
||||
if args.name:
|
||||
stack_name = args.name
|
||||
else:
|
||||
stack_name = 'baremetal'
|
||||
if args.quintupleo:
|
||||
stack_name = 'quintupleo'
|
||||
if not args.quintupleo:
|
||||
stack_template = 'templates/virtual-baremetal.yaml'
|
||||
else:
|
||||
stack_template = 'templates/quintupleo.yaml'
|
||||
return stack_name, stack_template
|
||||
|
||||
def _add_identifier(env_data, name, identifier, default=None, parameter=True):
|
||||
param_key = 'parameters'
|
||||
if not parameter:
|
||||
param_key = 'parameter_defaults'
|
||||
if param_key not in env_data or not env_data[param_key]:
|
||||
env_data[param_key] = {}
|
||||
original = env_data[param_key].get(name)
|
||||
if original is None:
|
||||
original = default
|
||||
if original is None:
|
||||
raise RuntimeError('No base value found when adding id')
|
||||
env_data[param_key][name] = '%s-%s' % (original, identifier)
|
||||
|
||||
def _generate_id_env(args):
|
||||
with open(args.env) as f:
|
||||
env_data = yaml.safe_load(f)
|
||||
_add_identifier(env_data, 'provision_net', args.id, default='provision')
|
||||
_add_identifier(env_data, 'public_net', args.id, default='public')
|
||||
_add_identifier(env_data, 'baremetal_prefix', args.id, default='baremetal')
|
||||
_add_identifier(env_data, 'bmc_prefix', args.id, default='bmc')
|
||||
_add_identifier(env_data, 'undercloud_name', args.id, default='undercloud')
|
||||
_add_identifier(env_data, 'overcloud_internal_net', args.id,
|
||||
default='internal', parameter=False)
|
||||
_add_identifier(env_data, 'overcloud_storage_net', args.id,
|
||||
default='storage', parameter=False)
|
||||
_add_identifier(env_data, 'overcloud_storage_mgmt_net', args.id,
|
||||
default='storage_mgmt', parameter=False)
|
||||
_add_identifier(env_data, 'overcloud_tenant_net', args.id,
|
||||
default='tenant', parameter=False)
|
||||
env_path = 'env-%s.yaml' % args.id
|
||||
with open(env_path, 'w') as f:
|
||||
yaml.safe_dump(env_data, f, default_flow_style=False)
|
||||
return env_path
|
||||
|
||||
def _get_heat_client():
|
||||
cloud = os.environ.get('OS_CLOUD')
|
||||
if cloud:
|
||||
import os_client_config
|
||||
return os_client_config.make_client('orchestration', cloud=cloud)
|
||||
else:
|
||||
username = os.environ.get('OS_USERNAME')
|
||||
password = os.environ.get('OS_PASSWORD')
|
||||
tenant = os.environ.get('OS_TENANT_NAME')
|
||||
auth_url = os.environ.get('OS_AUTH_URL')
|
||||
if not username or not password or not tenant or not auth_url:
|
||||
print('Source an appropriate rc file first')
|
||||
sys.exit(1)
|
||||
|
||||
# Get token for Heat to use
|
||||
kclient = keystone_client.Client(username=username, password=password,
|
||||
tenant_name=tenant, auth_url=auth_url)
|
||||
token_data = kclient.get_raw_token_from_identity_service(
|
||||
username=username,
|
||||
password=password,
|
||||
tenant_name=tenant,
|
||||
auth_url=auth_url)
|
||||
token_id = token_data['token']['id']
|
||||
# Get Heat endpoint
|
||||
for endpoint in token_data['serviceCatalog']:
|
||||
if endpoint['name'] == 'heat':
|
||||
# TODO: What if there's more than one endpoint?
|
||||
heat_endpoint = endpoint['endpoints'][0]['publicURL']
|
||||
|
||||
return heat_client.Client('1', endpoint=heat_endpoint, token=token_id)
|
||||
|
||||
def _deploy(stack_name, stack_template, env_path):
|
||||
hclient = _get_heat_client()
|
||||
|
||||
template_files, template = template_utils.get_template_contents(
|
||||
stack_template)
|
||||
env_files, env = template_utils.process_multiple_environments_and_files(
|
||||
['templates/resource-registry.yaml', env_path])
|
||||
all_files = {}
|
||||
all_files.update(template_files)
|
||||
all_files.update(env_files)
|
||||
|
||||
hclient.stacks.create(stack_name=stack_name,
|
||||
template=template,
|
||||
environment=env,
|
||||
files=all_files)
|
||||
|
||||
print 'Deployment of stack "%s" started.' % stack_name
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = _parse_args()
|
||||
env_path = args.env
|
||||
stack_name, stack_template = _process_args(args)
|
||||
if args.id:
|
||||
env_path = _generate_id_env(args)
|
||||
_deploy(stack_name, stack_template, env_path)
|
188
openstack_virtual_baremetal/openstackbmc.py
Executable file
188
openstack_virtual_baremetal/openstackbmc.py
Executable file
@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Virtual BMC for controlling OpenStack instances, based on fakebmc from
|
||||
# python-pyghmi
|
||||
|
||||
# Sample ipmitool commands:
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power on
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 power status
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 chassis bootdev pxe|disk
|
||||
# ipmitool -I lanplus -U admin -P password -H 127.0.0.1 mc reset cold
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
|
||||
from novaclient import client as novaclient
|
||||
from novaclient import exceptions
|
||||
import pyghmi.ipmi.bmc as bmc
|
||||
|
||||
|
||||
class OpenStackBmc(bmc.Bmc):
|
||||
def __init__(self, authdata, port, address, instance, user, password, tenant,
|
||||
auth_url):
|
||||
super(OpenStackBmc, self).__init__(authdata, port=port, address=address)
|
||||
self.novaclient = novaclient.Client(2, user, password,
|
||||
tenant, auth_url)
|
||||
self.instance = None
|
||||
# At times the bmc service is started before important things like
|
||||
# networking have fully initialized. Keep trying to find the
|
||||
# instance indefinitely, since there's no point in continuing if
|
||||
# we don't have an instance.
|
||||
while True:
|
||||
try:
|
||||
self.instance = self._find_instance(instance)
|
||||
if self.instance is not None:
|
||||
name = self.novaclient.servers.get(self.instance).name
|
||||
self.log('Managing instance: %s UUID: %s' %
|
||||
(name, self.instance))
|
||||
break
|
||||
except Exception as e:
|
||||
self.log('Exception finding instance "%s": %s' % (instance, e))
|
||||
time.sleep(1)
|
||||
|
||||
def _find_instance(self, instance):
|
||||
try:
|
||||
self.novaclient.servers.get(instance)
|
||||
return instance
|
||||
except exceptions.NotFound:
|
||||
name_regex = '^%s$' % instance
|
||||
i = self.novaclient.servers.list(search_opts={'name': name_regex})
|
||||
if len(i) > 1:
|
||||
self.log('Ambiguous instance name %s' % instance)
|
||||
sys.exit(1)
|
||||
try:
|
||||
return i[0].id
|
||||
except IndexError:
|
||||
self.log('Could not find specified instance %s' % instance)
|
||||
sys.exit(1)
|
||||
|
||||
def get_boot_device(self):
|
||||
server = self.novaclient.servers.get(self.instance)
|
||||
retval = 'network' if server.metadata.get('libvirt:pxe-first') else 'hd'
|
||||
self.log('Reporting boot device', retval)
|
||||
return retval
|
||||
|
||||
def set_boot_device(self, bootdevice):
|
||||
server = self.novaclient.servers.get(self.instance)
|
||||
if bootdevice == 'network':
|
||||
self.novaclient.servers.set_meta_item(server, 'libvirt:pxe-first', '1')
|
||||
else:
|
||||
self.novaclient.servers.set_meta_item(server, 'libvirt:pxe-first', '')
|
||||
self.log('Set boot device to', bootdevice)
|
||||
|
||||
def cold_reset(self):
|
||||
# Reset of the BMC, not managed system, here we will exit the demo
|
||||
self.log('Shutting down in response to BMC cold reset request')
|
||||
sys.exit(0)
|
||||
|
||||
def _instance_active(self):
|
||||
return self.novaclient.servers.get(self.instance).status == 'ACTIVE'
|
||||
|
||||
def get_power_state(self):
|
||||
self.log('Getting power state for %s' % self.instance)
|
||||
return self._instance_active()
|
||||
|
||||
def power_off(self):
|
||||
# this should be power down without waiting for clean shutdown
|
||||
if self._instance_active():
|
||||
try:
|
||||
self.novaclient.servers.stop(self.instance)
|
||||
self.log('Powered off %s' % self.instance)
|
||||
except exceptions.Conflict as e:
|
||||
# This can happen if we get two requests to start a server in
|
||||
# short succession. The instance may then be in a powering-on
|
||||
# state, which means it is invalid to start it again.
|
||||
self.log('Ignoring exception: "%s"' % e)
|
||||
else:
|
||||
self.log('%s is already off.' % self.instance)
|
||||
return 0xd5
|
||||
|
||||
def power_on(self):
|
||||
if not self._instance_active():
|
||||
try:
|
||||
self.novaclient.servers.start(self.instance)
|
||||
self.log('Powered on %s' % self.instance)
|
||||
except exceptions.Conflict as e:
|
||||
# This can happen if we get two requests to start a server in
|
||||
# short succession. The instance may then be in a powering-on
|
||||
# state, which means it is invalid to start it again.
|
||||
self.log('Ignoring exception: "%s"' % e)
|
||||
else:
|
||||
self.log('%s is already on.' % self.instance)
|
||||
return 0xd5
|
||||
|
||||
def power_reset(self):
|
||||
pass
|
||||
|
||||
def power_shutdown(self):
|
||||
# should attempt a clean shutdown
|
||||
self.novaclient.servers.stop(self.instance)
|
||||
self.log('Politely shut down %s' % self.instance)
|
||||
|
||||
def log(self, *msg):
|
||||
print(' '.join(msg))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='openstackbmc',
|
||||
description='Virtual BMC for controlling OpenStack instance',
|
||||
)
|
||||
parser.add_argument('--port',
|
||||
dest='port',
|
||||
type=int,
|
||||
default=623,
|
||||
help='Port to listen on; defaults to 623')
|
||||
parser.add_argument('--address',
|
||||
dest='address',
|
||||
default='::',
|
||||
help='Address to bind to; defaults to ::')
|
||||
parser.add_argument('--instance',
|
||||
dest='instance',
|
||||
required=True,
|
||||
help='The uuid or name of the OpenStack instance to manage')
|
||||
parser.add_argument('--os-user',
|
||||
dest='user',
|
||||
required=True,
|
||||
help='The user for connecting to OpenStack')
|
||||
parser.add_argument('--os-password',
|
||||
dest='password',
|
||||
required=True,
|
||||
help='The password for connecting to OpenStack')
|
||||
parser.add_argument('--os-tenant',
|
||||
dest='tenant',
|
||||
required=True,
|
||||
help='The tenant for connecting to OpenStack')
|
||||
parser.add_argument('--os-auth-url',
|
||||
dest='auth_url',
|
||||
required=True,
|
||||
help='The OpenStack Keystone auth url')
|
||||
args = parser.parse_args()
|
||||
mybmc = OpenStackBmc({'admin': 'password'}, port=args.port,
|
||||
address='::ffff:%s' % args.address,
|
||||
instance=args.instance,
|
||||
user=args.user,
|
||||
password=args.password,
|
||||
tenant=args.tenant,
|
||||
auth_url=args.auth_url)
|
||||
mybmc.listen()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
0
openstack_virtual_baremetal/tests/__init__.py
Normal file
0
openstack_virtual_baremetal/tests/__init__.py
Normal file
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
pyghmi
|
||||
PyYAML
|
||||
python-heatclient
|
||||
python-keystoneclient
|
||||
python-novaclient
|
33
setup.cfg
Normal file
33
setup.cfg
Normal file
@ -0,0 +1,33 @@
|
||||
[metadata]
|
||||
name = openstack-virtual-baremetal
|
||||
summary = A collection of tools for using OpenStack instances to test baremetal deployment
|
||||
description-file =
|
||||
README.rst
|
||||
author = Ben Nemec
|
||||
author-email = bnemec@redhat.com
|
||||
home-page = http://www.redhat.com/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
openstack_virtual_baremetal
|
||||
|
||||
#[entry_points]
|
||||
#console_scripts =
|
||||
# dlrn-repo = dlrn_repo.main:main
|
||||
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
build-dir = doc/build
|
||||
source-dir = doc/source
|
22
setup.py
Normal file
22
setup.py
Normal file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
7
test-requirements.txt
Normal file
7
test-requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
coverage>=3.6
|
||||
discover
|
||||
fixtures>=0.3.14
|
||||
python-subunit>=0.0.18
|
||||
testrepository>=0.0.18
|
||||
testtools>=0.9.36,!=1.2.0
|
||||
mock>=1.0
|
29
tox.ini
Normal file
29
tox.ini
Normal file
@ -0,0 +1,29 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
envlist = py34,py27,pep8
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
-r{toxinidir}/requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:pep8]
|
||||
deps = flake8
|
||||
commands = flake8
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py test --coverage --coverage-package-name=openstack_virtual_baremetal --testr-args='{posargs}'
|
||||
|
||||
[flake8]
|
||||
ignore = H803
|
||||
show-source = True
|
||||
exclude = .tox,dist,doc,*.egg,build
|
Loading…
Reference in New Issue
Block a user