synced /next
This commit is contained in:
commit
8c04757bc9
150
README.md
150
README.md
@ -27,14 +27,16 @@ This charm also supports scale out and high availability using the hacluster cha
|
|||||||
|
|
||||||
The minimum openstack-origin-git config required to deploy from source is:
|
The minimum openstack-origin-git config required to deploy from source is:
|
||||||
|
|
||||||
openstack-origin-git:
|
openstack-origin-git: include-file://neutron-juno.yaml
|
||||||
"repositories:
|
|
||||||
- {name: requirements,
|
neutron-juno.yaml
|
||||||
repository: 'git://git.openstack.org/openstack/requirements',
|
repositories:
|
||||||
branch: stable/juno}
|
- {name: requirements,
|
||||||
- {name: neutron,
|
repository: 'git://github.com/openstack/requirements',
|
||||||
repository: 'git://git.openstack.org/openstack/neutron',
|
branch: stable/juno}
|
||||||
branch: stable/juno}"
|
- {name: neutron,
|
||||||
|
repository: 'git://github.com/openstack/neutron',
|
||||||
|
branch: stable/juno}
|
||||||
|
|
||||||
Note that there are only two 'name' values the charm knows about: 'requirements'
|
Note that there are only two 'name' values the charm knows about: 'requirements'
|
||||||
and 'neutron'. These repositories must correspond to these 'name' values.
|
and 'neutron'. These repositories must correspond to these 'name' values.
|
||||||
@ -44,71 +46,73 @@ in the order in which they are specified.
|
|||||||
|
|
||||||
The following is a full list of current tip repos (may not be up-to-date):
|
The following is a full list of current tip repos (may not be up-to-date):
|
||||||
|
|
||||||
openstack-origin-git:
|
openstack-origin-git: include-file://neutron-master.yaml
|
||||||
"repositories:
|
|
||||||
- {name: requirements,
|
neutron-master.yaml
|
||||||
repository: 'git://git.openstack.org/openstack/requirements',
|
repositories:
|
||||||
branch: master}
|
- {name: requirements,
|
||||||
- {name: oslo-concurrency,
|
repository: 'git://github.com/openstack/requirements',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.concurrency',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-concurrency,
|
||||||
- {name: oslo-config,
|
repository: 'git://github.com/openstack/oslo.concurrency',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.config',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-config,
|
||||||
- {name: oslo-context,
|
repository: 'git://github.com/openstack/oslo.config',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.context.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-context,
|
||||||
- {name: oslo-db,
|
repository: 'git://github.com/openstack/oslo.context',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.db',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-db,
|
||||||
- {name: oslo-i18n,
|
repository: 'git://github.com/openstack/oslo.db',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.i18n',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-i18n,
|
||||||
- {name: oslo-messaging,
|
repository: 'git://github.com/openstack/oslo.i18n',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.messaging.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-messaging,
|
||||||
- {name: oslo-middleware,
|
repository: 'git://github.com/openstack/oslo.messaging',
|
||||||
repository': 'git://git.openstack.org/openstack/oslo.middleware.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-middleware,
|
||||||
- {name: oslo-rootwrap',
|
repository': 'git://github.com/openstack/oslo.middleware',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.rootwrap.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-rootwrap',
|
||||||
- {name: oslo-serialization,
|
repository: 'git://github.com/openstack/oslo.rootwrap',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.serialization',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-serialization,
|
||||||
- {name: oslo-utils,
|
repository: 'git://github.com/openstack/oslo.serialization',
|
||||||
repository: 'git://git.openstack.org/openstack/oslo.utils',
|
branch: master}
|
||||||
branch: master}
|
- {name: oslo-utils,
|
||||||
- {name: pbr,
|
repository: 'git://github.com/openstack/oslo.utils',
|
||||||
repository: 'git://git.openstack.org/openstack-dev/pbr',
|
branch: master}
|
||||||
branch: master}
|
- {name: pbr,
|
||||||
- {name: stevedore,
|
repository: 'git://github.com/openstack-dev/pbr',
|
||||||
repository: 'git://git.openstack.org/openstack/stevedore.git',
|
branch: master}
|
||||||
branch: 'master'}
|
- {name: stevedore,
|
||||||
- {name: python-keystoneclient,
|
repository: 'git://github.com/openstack/stevedore',
|
||||||
repository: 'git://git.openstack.org/openstack/python-keystoneclient',
|
branch: 'master'}
|
||||||
branch: master}
|
- {name: python-keystoneclient,
|
||||||
- {name: python-neutronclient,
|
repository: 'git://github.com/openstack/python-keystoneclient',
|
||||||
repository: 'git://git.openstack.org/openstack/python-neutronclient.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: python-neutronclient,
|
||||||
- {name: python-novaclient,
|
repository: 'git://github.com/openstack/python-neutronclient',
|
||||||
repository': 'git://git.openstack.org/openstack/python-novaclient.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: python-novaclient,
|
||||||
- {name: keystonemiddleware,
|
repository': 'git://github.com/openstack/python-novaclient',
|
||||||
repository: 'git://git.openstack.org/openstack/keystonemiddleware',
|
branch: master}
|
||||||
branch: master}
|
- {name: keystonemiddleware,
|
||||||
- {name: neutron-fwaas,
|
repository: 'git://github.com/openstack/keystonemiddleware',
|
||||||
repository': 'git://git.openstack.org/openstack/neutron-fwaas.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: neutron-fwaas,
|
||||||
- {name: neutron-lbaas,
|
repository': 'git://github.com/openstack/neutron-fwaas',
|
||||||
repository: 'git://git.openstack.org/openstack/neutron-lbaas.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: neutron-lbaas,
|
||||||
- {name: neutron-vpnaas,
|
repository: 'git://github.com/openstack/neutron-lbaas',
|
||||||
repository: 'git://git.openstack.org/openstack/neutron-vpnaas.git',
|
branch: master}
|
||||||
branch: master}
|
- {name: neutron-vpnaas,
|
||||||
- {name: neutron,
|
repository: 'git://github.com/openstack/neutron-vpnaas',
|
||||||
repository: 'git://git.openstack.org/openstack/neutron',
|
branch: master}
|
||||||
branch: master}"
|
- {name: neutron,
|
||||||
|
repository: 'git://github.com/openstack/neutron',
|
||||||
|
branch: master}
|
||||||
|
|
||||||
# Restrictions
|
# Restrictions
|
||||||
|
|
||||||
|
11
config.yaml
11
config.yaml
@ -85,11 +85,12 @@ options:
|
|||||||
default: gre
|
default: gre
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
Overlay network type to use choose one of:
|
Overlay network types to use, valid options include:
|
||||||
.
|
.
|
||||||
gre
|
gre
|
||||||
vxlan
|
vxlan
|
||||||
.
|
.
|
||||||
|
Multiple types can be provided - field is space delimited.
|
||||||
flat-network-providers:
|
flat-network-providers:
|
||||||
type: string
|
type: string
|
||||||
default:
|
default:
|
||||||
@ -99,8 +100,10 @@ options:
|
|||||||
type: string
|
type: string
|
||||||
default: "physnet1:1000:2000"
|
default: "physnet1:1000:2000"
|
||||||
description: |
|
description: |
|
||||||
Space-delimited list of Neutron network-provider & vlan-id-ranges using
|
Space-delimited list of <physical_network>:<vlan_min>:<vlan_max> or
|
||||||
the following format "<provider>:<start>:<end> ...".
|
<physical_network> specifying physical_network names usable for VLAN
|
||||||
|
provider and tenant networks, as well as ranges of VLAN tags on each
|
||||||
|
available for allocation to tenant networks.
|
||||||
# Quota configuration settings
|
# Quota configuration settings
|
||||||
quota-security-group:
|
quota-security-group:
|
||||||
default: 10
|
default: 10
|
||||||
|
@ -52,6 +52,8 @@ from charmhelpers.core.strutils import (
|
|||||||
bool_from_string,
|
bool_from_string,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DC_RESOURCE_NAME = 'DC'
|
||||||
|
|
||||||
|
|
||||||
class HAIncompleteConfig(Exception):
|
class HAIncompleteConfig(Exception):
|
||||||
pass
|
pass
|
||||||
@ -95,6 +97,27 @@ def is_clustered():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_crm_dc():
|
||||||
|
"""
|
||||||
|
Determine leadership by querying the pacemaker Designated Controller
|
||||||
|
"""
|
||||||
|
cmd = ['crm', 'status']
|
||||||
|
try:
|
||||||
|
status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||||
|
if not isinstance(status, six.text_type):
|
||||||
|
status = six.text_type(status, "utf-8")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return False
|
||||||
|
current_dc = ''
|
||||||
|
for line in status.split('\n'):
|
||||||
|
if line.startswith('Current DC'):
|
||||||
|
# Current DC: juju-lytrusty-machine-2 (168108163) - partition with quorum
|
||||||
|
current_dc = line.split(':')[1].split()[0]
|
||||||
|
if current_dc == get_unit_hostname():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound)
|
@retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound)
|
||||||
def is_crm_leader(resource, retry=False):
|
def is_crm_leader(resource, retry=False):
|
||||||
"""
|
"""
|
||||||
@ -104,6 +127,8 @@ def is_crm_leader(resource, retry=False):
|
|||||||
We allow this operation to be retried to avoid the possibility of getting a
|
We allow this operation to be retried to avoid the possibility of getting a
|
||||||
false negative. See LP #1396246 for more info.
|
false negative. See LP #1396246 for more info.
|
||||||
"""
|
"""
|
||||||
|
if resource == DC_RESOURCE_NAME:
|
||||||
|
return is_crm_dc()
|
||||||
cmd = ['crm', 'resource', 'show', resource]
|
cmd = ['crm', 'resource', 'show', resource]
|
||||||
try:
|
try:
|
||||||
status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||||
|
@ -109,7 +109,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
# Must be ordered by OpenStack release (not by Ubuntu release):
|
# Must be ordered by OpenStack release (not by Ubuntu release):
|
||||||
(self.precise_essex, self.precise_folsom, self.precise_grizzly,
|
(self.precise_essex, self.precise_folsom, self.precise_grizzly,
|
||||||
self.precise_havana, self.precise_icehouse,
|
self.precise_havana, self.precise_icehouse,
|
||||||
self.trusty_icehouse, self.trusty_juno, self.utopic_juno,
|
self.trusty_icehouse, self.trusty_juno, self.utopic_juno,
|
||||||
self.trusty_kilo, self.vivid_kilo) = range(10)
|
self.trusty_kilo, self.vivid_kilo) = range(10)
|
||||||
|
|
||||||
releases = {
|
releases = {
|
||||||
|
@ -258,11 +258,14 @@ def network_manager():
|
|||||||
def parse_mappings(mappings):
|
def parse_mappings(mappings):
|
||||||
parsed = {}
|
parsed = {}
|
||||||
if mappings:
|
if mappings:
|
||||||
mappings = mappings.split(' ')
|
mappings = mappings.split()
|
||||||
for m in mappings:
|
for m in mappings:
|
||||||
p = m.partition(':')
|
p = m.partition(':')
|
||||||
if p[1] == ':':
|
key = p[0].strip()
|
||||||
parsed[p[0].strip()] = p[2].strip()
|
if p[1]:
|
||||||
|
parsed[key] = p[2].strip()
|
||||||
|
else:
|
||||||
|
parsed[key] = ''
|
||||||
|
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
@ -285,13 +288,13 @@ def parse_data_port_mappings(mappings, default_bridge='br-data'):
|
|||||||
Returns dict of the form {bridge:port}.
|
Returns dict of the form {bridge:port}.
|
||||||
"""
|
"""
|
||||||
_mappings = parse_mappings(mappings)
|
_mappings = parse_mappings(mappings)
|
||||||
if not _mappings:
|
if not _mappings or list(_mappings.values()) == ['']:
|
||||||
if not mappings:
|
if not mappings:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# For backwards-compatibility we need to support port-only provided in
|
# For backwards-compatibility we need to support port-only provided in
|
||||||
# config.
|
# config.
|
||||||
_mappings = {default_bridge: mappings.split(' ')[0]}
|
_mappings = {default_bridge: mappings.split()[0]}
|
||||||
|
|
||||||
bridges = _mappings.keys()
|
bridges = _mappings.keys()
|
||||||
ports = _mappings.values()
|
ports = _mappings.values()
|
||||||
@ -311,6 +314,8 @@ def parse_vlan_range_mappings(mappings):
|
|||||||
|
|
||||||
Mappings must be a space-delimited list of provider:start:end mappings.
|
Mappings must be a space-delimited list of provider:start:end mappings.
|
||||||
|
|
||||||
|
The start:end range is optional and may be omitted.
|
||||||
|
|
||||||
Returns dict of the form {provider: (start, end)}.
|
Returns dict of the form {provider: (start, end)}.
|
||||||
"""
|
"""
|
||||||
_mappings = parse_mappings(mappings)
|
_mappings = parse_mappings(mappings)
|
||||||
|
@ -53,9 +53,13 @@ from charmhelpers.contrib.network.ip import (
|
|||||||
get_ipv6_addr
|
get_ipv6_addr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from charmhelpers.contrib.python.packages import (
|
||||||
|
pip_create_virtualenv,
|
||||||
|
pip_install,
|
||||||
|
)
|
||||||
|
|
||||||
from charmhelpers.core.host import lsb_release, mounts, umount
|
from charmhelpers.core.host import lsb_release, mounts, umount
|
||||||
from charmhelpers.fetch import apt_install, apt_cache, install_remote
|
from charmhelpers.fetch import apt_install, apt_cache, install_remote
|
||||||
from charmhelpers.contrib.python.packages import pip_install
|
|
||||||
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
|
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
|
||||||
from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
|
from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
|
||||||
|
|
||||||
@ -497,7 +501,17 @@ def git_install_requested():
|
|||||||
requirements_dir = None
|
requirements_dir = None
|
||||||
|
|
||||||
|
|
||||||
def git_clone_and_install(projects_yaml, core_project):
|
def _git_yaml_load(projects_yaml):
|
||||||
|
"""
|
||||||
|
Load the specified yaml into a dictionary.
|
||||||
|
"""
|
||||||
|
if not projects_yaml:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return yaml.load(projects_yaml)
|
||||||
|
|
||||||
|
|
||||||
|
def git_clone_and_install(projects_yaml, core_project, depth=1):
|
||||||
"""
|
"""
|
||||||
Clone/install all specified OpenStack repositories.
|
Clone/install all specified OpenStack repositories.
|
||||||
|
|
||||||
@ -510,23 +524,22 @@ def git_clone_and_install(projects_yaml, core_project):
|
|||||||
repository: 'git://git.openstack.org/openstack/requirements.git',
|
repository: 'git://git.openstack.org/openstack/requirements.git',
|
||||||
branch: 'stable/icehouse'}
|
branch: 'stable/icehouse'}
|
||||||
directory: /mnt/openstack-git
|
directory: /mnt/openstack-git
|
||||||
http_proxy: http://squid.internal:3128
|
http_proxy: squid-proxy-url
|
||||||
https_proxy: https://squid.internal:3128
|
https_proxy: squid-proxy-url
|
||||||
|
|
||||||
The directory, http_proxy, and https_proxy keys are optional.
|
The directory, http_proxy, and https_proxy keys are optional.
|
||||||
"""
|
"""
|
||||||
global requirements_dir
|
global requirements_dir
|
||||||
parent_dir = '/mnt/openstack-git'
|
parent_dir = '/mnt/openstack-git'
|
||||||
|
http_proxy = None
|
||||||
|
|
||||||
if not projects_yaml:
|
projects = _git_yaml_load(projects_yaml)
|
||||||
return
|
|
||||||
|
|
||||||
projects = yaml.load(projects_yaml)
|
|
||||||
_git_validate_projects_yaml(projects, core_project)
|
_git_validate_projects_yaml(projects, core_project)
|
||||||
|
|
||||||
old_environ = dict(os.environ)
|
old_environ = dict(os.environ)
|
||||||
|
|
||||||
if 'http_proxy' in projects.keys():
|
if 'http_proxy' in projects.keys():
|
||||||
|
http_proxy = projects['http_proxy']
|
||||||
os.environ['http_proxy'] = projects['http_proxy']
|
os.environ['http_proxy'] = projects['http_proxy']
|
||||||
if 'https_proxy' in projects.keys():
|
if 'https_proxy' in projects.keys():
|
||||||
os.environ['https_proxy'] = projects['https_proxy']
|
os.environ['https_proxy'] = projects['https_proxy']
|
||||||
@ -534,15 +547,19 @@ def git_clone_and_install(projects_yaml, core_project):
|
|||||||
if 'directory' in projects.keys():
|
if 'directory' in projects.keys():
|
||||||
parent_dir = projects['directory']
|
parent_dir = projects['directory']
|
||||||
|
|
||||||
|
pip_create_virtualenv(os.path.join(parent_dir, 'venv'))
|
||||||
|
|
||||||
for p in projects['repositories']:
|
for p in projects['repositories']:
|
||||||
repo = p['repository']
|
repo = p['repository']
|
||||||
branch = p['branch']
|
branch = p['branch']
|
||||||
if p['name'] == 'requirements':
|
if p['name'] == 'requirements':
|
||||||
repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
|
repo_dir = _git_clone_and_install_single(repo, branch, depth,
|
||||||
|
parent_dir, http_proxy,
|
||||||
update_requirements=False)
|
update_requirements=False)
|
||||||
requirements_dir = repo_dir
|
requirements_dir = repo_dir
|
||||||
else:
|
else:
|
||||||
repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
|
repo_dir = _git_clone_and_install_single(repo, branch, depth,
|
||||||
|
parent_dir, http_proxy,
|
||||||
update_requirements=True)
|
update_requirements=True)
|
||||||
|
|
||||||
os.environ = old_environ
|
os.environ = old_environ
|
||||||
@ -574,7 +591,8 @@ def _git_ensure_key_exists(key, keys):
|
|||||||
error_out('openstack-origin-git key \'{}\' is missing'.format(key))
|
error_out('openstack-origin-git key \'{}\' is missing'.format(key))
|
||||||
|
|
||||||
|
|
||||||
def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):
|
def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,
|
||||||
|
update_requirements):
|
||||||
"""
|
"""
|
||||||
Clone and install a single git repository.
|
Clone and install a single git repository.
|
||||||
"""
|
"""
|
||||||
@ -587,7 +605,8 @@ def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements)
|
|||||||
|
|
||||||
if not os.path.exists(dest_dir):
|
if not os.path.exists(dest_dir):
|
||||||
juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
|
juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
|
||||||
repo_dir = install_remote(repo, dest=parent_dir, branch=branch)
|
repo_dir = install_remote(repo, dest=parent_dir, branch=branch,
|
||||||
|
depth=depth)
|
||||||
else:
|
else:
|
||||||
repo_dir = dest_dir
|
repo_dir = dest_dir
|
||||||
|
|
||||||
@ -598,7 +617,12 @@ def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements)
|
|||||||
_git_update_requirements(repo_dir, requirements_dir)
|
_git_update_requirements(repo_dir, requirements_dir)
|
||||||
|
|
||||||
juju_log('Installing git repo from dir: {}'.format(repo_dir))
|
juju_log('Installing git repo from dir: {}'.format(repo_dir))
|
||||||
pip_install(repo_dir)
|
if http_proxy:
|
||||||
|
pip_install(repo_dir, proxy=http_proxy,
|
||||||
|
venv=os.path.join(parent_dir, 'venv'))
|
||||||
|
else:
|
||||||
|
pip_install(repo_dir,
|
||||||
|
venv=os.path.join(parent_dir, 'venv'))
|
||||||
|
|
||||||
return repo_dir
|
return repo_dir
|
||||||
|
|
||||||
@ -621,16 +645,27 @@ def _git_update_requirements(package_dir, reqs_dir):
|
|||||||
os.chdir(orig_dir)
|
os.chdir(orig_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def git_pip_venv_dir(projects_yaml):
|
||||||
|
"""
|
||||||
|
Return the pip virtualenv path.
|
||||||
|
"""
|
||||||
|
parent_dir = '/mnt/openstack-git'
|
||||||
|
|
||||||
|
projects = _git_yaml_load(projects_yaml)
|
||||||
|
|
||||||
|
if 'directory' in projects.keys():
|
||||||
|
parent_dir = projects['directory']
|
||||||
|
|
||||||
|
return os.path.join(parent_dir, 'venv')
|
||||||
|
|
||||||
|
|
||||||
def git_src_dir(projects_yaml, project):
|
def git_src_dir(projects_yaml, project):
|
||||||
"""
|
"""
|
||||||
Return the directory where the specified project's source is located.
|
Return the directory where the specified project's source is located.
|
||||||
"""
|
"""
|
||||||
parent_dir = '/mnt/openstack-git'
|
parent_dir = '/mnt/openstack-git'
|
||||||
|
|
||||||
if not projects_yaml:
|
projects = _git_yaml_load(projects_yaml)
|
||||||
return
|
|
||||||
|
|
||||||
projects = yaml.load(projects_yaml)
|
|
||||||
|
|
||||||
if 'directory' in projects.keys():
|
if 'directory' in projects.keys():
|
||||||
parent_dir = projects['directory']
|
parent_dir = projects['directory']
|
||||||
@ -640,3 +675,15 @@ def git_src_dir(projects_yaml, project):
|
|||||||
return os.path.join(parent_dir, os.path.basename(p['repository']))
|
return os.path.join(parent_dir, os.path.basename(p['repository']))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def git_yaml_value(projects_yaml, key):
|
||||||
|
"""
|
||||||
|
Return the value in projects_yaml for the specified key.
|
||||||
|
"""
|
||||||
|
projects = _git_yaml_load(projects_yaml)
|
||||||
|
|
||||||
|
if key in projects.keys():
|
||||||
|
return projects[key]
|
||||||
|
|
||||||
|
return None
|
||||||
|
@ -17,8 +17,11 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from charmhelpers.fetch import apt_install, apt_update
|
from charmhelpers.fetch import apt_install, apt_update
|
||||||
from charmhelpers.core.hookenv import log
|
from charmhelpers.core.hookenv import charm_dir, log
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pip import main as pip_execute
|
from pip import main as pip_execute
|
||||||
@ -51,11 +54,15 @@ def pip_install_requirements(requirements, **options):
|
|||||||
pip_execute(command)
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
def pip_install(package, fatal=False, upgrade=False, **options):
|
def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
|
||||||
"""Install a python package"""
|
"""Install a python package"""
|
||||||
command = ["install"]
|
if venv:
|
||||||
|
venv_python = os.path.join(venv, 'bin/pip')
|
||||||
|
command = [venv_python, "install"]
|
||||||
|
else:
|
||||||
|
command = ["install"]
|
||||||
|
|
||||||
available_options = ('proxy', 'src', 'log', "index-url", )
|
available_options = ('proxy', 'src', 'log', 'index-url', )
|
||||||
for option in parse_options(options, available_options):
|
for option in parse_options(options, available_options):
|
||||||
command.append(option)
|
command.append(option)
|
||||||
|
|
||||||
@ -69,7 +76,10 @@ def pip_install(package, fatal=False, upgrade=False, **options):
|
|||||||
|
|
||||||
log("Installing {} package with options: {}".format(package,
|
log("Installing {} package with options: {}".format(package,
|
||||||
command))
|
command))
|
||||||
pip_execute(command)
|
if venv:
|
||||||
|
subprocess.check_call(command)
|
||||||
|
else:
|
||||||
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
def pip_uninstall(package, **options):
|
def pip_uninstall(package, **options):
|
||||||
@ -94,3 +104,16 @@ def pip_list():
|
|||||||
"""Returns the list of current python installed packages
|
"""Returns the list of current python installed packages
|
||||||
"""
|
"""
|
||||||
return pip_execute(["list"])
|
return pip_execute(["list"])
|
||||||
|
|
||||||
|
|
||||||
|
def pip_create_virtualenv(path=None):
|
||||||
|
"""Create an isolated Python environment."""
|
||||||
|
apt_install('python-virtualenv')
|
||||||
|
|
||||||
|
if path:
|
||||||
|
venv_path = path
|
||||||
|
else:
|
||||||
|
venv_path = os.path.join(charm_dir(), 'venv')
|
||||||
|
|
||||||
|
if not os.path.exists(venv_path):
|
||||||
|
subprocess.check_call(['virtualenv', venv_path])
|
||||||
|
@ -21,12 +21,14 @@
|
|||||||
# Charm Helpers Developers <juju@lists.ubuntu.com>
|
# Charm Helpers Developers <juju@lists.ubuntu.com>
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
from functools import wraps
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import errno
|
import errno
|
||||||
|
import tempfile
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
|
|
||||||
import six
|
import six
|
||||||
@ -58,15 +60,17 @@ def cached(func):
|
|||||||
|
|
||||||
will cache the result of unit_get + 'test' for future calls.
|
will cache the result of unit_get + 'test' for future calls.
|
||||||
"""
|
"""
|
||||||
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
global cache
|
global cache
|
||||||
key = str((func, args, kwargs))
|
key = str((func, args, kwargs))
|
||||||
try:
|
try:
|
||||||
return cache[key]
|
return cache[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
res = func(*args, **kwargs)
|
pass # Drop out of the exception handler scope.
|
||||||
cache[key] = res
|
res = func(*args, **kwargs)
|
||||||
return res
|
cache[key] = res
|
||||||
|
return res
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +182,7 @@ def local_unit():
|
|||||||
|
|
||||||
def remote_unit():
|
def remote_unit():
|
||||||
"""The remote unit for the current relation hook"""
|
"""The remote unit for the current relation hook"""
|
||||||
return os.environ['JUJU_REMOTE_UNIT']
|
return os.environ.get('JUJU_REMOTE_UNIT', None)
|
||||||
|
|
||||||
|
|
||||||
def service_name():
|
def service_name():
|
||||||
@ -250,6 +254,12 @@ class Config(dict):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return (self._prev_dict or {})[key]
|
return (self._prev_dict or {})[key]
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
prev_keys = []
|
prev_keys = []
|
||||||
if self._prev_dict is not None:
|
if self._prev_dict is not None:
|
||||||
@ -353,18 +363,49 @@ def relation_set(relation_id=None, relation_settings=None, **kwargs):
|
|||||||
"""Set relation information for the current unit"""
|
"""Set relation information for the current unit"""
|
||||||
relation_settings = relation_settings if relation_settings else {}
|
relation_settings = relation_settings if relation_settings else {}
|
||||||
relation_cmd_line = ['relation-set']
|
relation_cmd_line = ['relation-set']
|
||||||
|
accepts_file = "--file" in subprocess.check_output(
|
||||||
|
relation_cmd_line + ["--help"], universal_newlines=True)
|
||||||
if relation_id is not None:
|
if relation_id is not None:
|
||||||
relation_cmd_line.extend(('-r', relation_id))
|
relation_cmd_line.extend(('-r', relation_id))
|
||||||
for k, v in (list(relation_settings.items()) + list(kwargs.items())):
|
settings = relation_settings.copy()
|
||||||
if v is None:
|
settings.update(kwargs)
|
||||||
relation_cmd_line.append('{}='.format(k))
|
for key, value in settings.items():
|
||||||
else:
|
# Force value to be a string: it always should, but some call
|
||||||
relation_cmd_line.append('{}={}'.format(k, v))
|
# sites pass in things like dicts or numbers.
|
||||||
subprocess.check_call(relation_cmd_line)
|
if value is not None:
|
||||||
|
settings[key] = "{}".format(value)
|
||||||
|
if accepts_file:
|
||||||
|
# --file was introduced in Juju 1.23.2. Use it by default if
|
||||||
|
# available, since otherwise we'll break if the relation data is
|
||||||
|
# too big. Ideally we should tell relation-set to read the data from
|
||||||
|
# stdin, but that feature is broken in 1.23.2: Bug #1454678.
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False) as settings_file:
|
||||||
|
settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
|
||||||
|
subprocess.check_call(
|
||||||
|
relation_cmd_line + ["--file", settings_file.name])
|
||||||
|
os.remove(settings_file.name)
|
||||||
|
else:
|
||||||
|
for key, value in settings.items():
|
||||||
|
if value is None:
|
||||||
|
relation_cmd_line.append('{}='.format(key))
|
||||||
|
else:
|
||||||
|
relation_cmd_line.append('{}={}'.format(key, value))
|
||||||
|
subprocess.check_call(relation_cmd_line)
|
||||||
# Flush cache of any relation-gets for local unit
|
# Flush cache of any relation-gets for local unit
|
||||||
flush(local_unit())
|
flush(local_unit())
|
||||||
|
|
||||||
|
|
||||||
|
def relation_clear(r_id=None):
|
||||||
|
''' Clears any relation data already set on relation r_id '''
|
||||||
|
settings = relation_get(rid=r_id,
|
||||||
|
unit=local_unit())
|
||||||
|
for setting in settings:
|
||||||
|
if setting not in ['public-address', 'private-address']:
|
||||||
|
settings[setting] = None
|
||||||
|
relation_set(relation_id=r_id,
|
||||||
|
**settings)
|
||||||
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def relation_ids(reltype=None):
|
def relation_ids(reltype=None):
|
||||||
"""A list of relation_ids"""
|
"""A list of relation_ids"""
|
||||||
@ -509,6 +550,11 @@ def unit_get(attribute):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def unit_public_ip():
|
||||||
|
"""Get this unit's public IP address"""
|
||||||
|
return unit_get('public-address')
|
||||||
|
|
||||||
|
|
||||||
def unit_private_ip():
|
def unit_private_ip():
|
||||||
"""Get this unit's private IP address"""
|
"""Get this unit's private IP address"""
|
||||||
return unit_get('private-address')
|
return unit_get('private-address')
|
||||||
@ -605,3 +651,94 @@ def action_fail(message):
|
|||||||
|
|
||||||
The results set by action_set are preserved."""
|
The results set by action_set are preserved."""
|
||||||
subprocess.check_call(['action-fail', message])
|
subprocess.check_call(['action-fail', message])
|
||||||
|
|
||||||
|
|
||||||
|
def status_set(workload_state, message):
|
||||||
|
"""Set the workload state with a message
|
||||||
|
|
||||||
|
Use status-set to set the workload state with a message which is visible
|
||||||
|
to the user via juju status. If the status-set command is not found then
|
||||||
|
assume this is juju < 1.23 and juju-log the message unstead.
|
||||||
|
|
||||||
|
workload_state -- valid juju workload state.
|
||||||
|
message -- status update message
|
||||||
|
"""
|
||||||
|
valid_states = ['maintenance', 'blocked', 'waiting', 'active']
|
||||||
|
if workload_state not in valid_states:
|
||||||
|
raise ValueError(
|
||||||
|
'{!r} is not a valid workload state'.format(workload_state)
|
||||||
|
)
|
||||||
|
cmd = ['status-set', workload_state, message]
|
||||||
|
try:
|
||||||
|
ret = subprocess.call(cmd)
|
||||||
|
if ret == 0:
|
||||||
|
return
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
|
log_message = 'status-set failed: {} {}'.format(workload_state,
|
||||||
|
message)
|
||||||
|
log(log_message, level='INFO')
|
||||||
|
|
||||||
|
|
||||||
|
def status_get():
|
||||||
|
"""Retrieve the previously set juju workload state
|
||||||
|
|
||||||
|
If the status-set command is not found then assume this is juju < 1.23 and
|
||||||
|
return 'unknown'
|
||||||
|
"""
|
||||||
|
cmd = ['status-get']
|
||||||
|
try:
|
||||||
|
raw_status = subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
status = raw_status.rstrip()
|
||||||
|
return status
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
return 'unknown'
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def translate_exc(from_exc, to_exc):
|
||||||
|
def inner_translate_exc1(f):
|
||||||
|
def inner_translate_exc2(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
except from_exc:
|
||||||
|
raise to_exc
|
||||||
|
|
||||||
|
return inner_translate_exc2
|
||||||
|
|
||||||
|
return inner_translate_exc1
|
||||||
|
|
||||||
|
|
||||||
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
|
def is_leader():
|
||||||
|
"""Does the current unit hold the juju leadership
|
||||||
|
|
||||||
|
Uses juju to determine whether the current unit is the leader of its peers
|
||||||
|
"""
|
||||||
|
cmd = ['is-leader', '--format=json']
|
||||||
|
return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
|
def leader_get(attribute=None):
|
||||||
|
"""Juju leader get value(s)"""
|
||||||
|
cmd = ['leader-get', '--format=json'] + [attribute or '-']
|
||||||
|
return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
|
def leader_set(settings=None, **kwargs):
|
||||||
|
"""Juju leader set value(s)"""
|
||||||
|
log("Juju leader-set '%s'" % (settings), level=DEBUG)
|
||||||
|
cmd = ['leader-set']
|
||||||
|
settings = settings or {}
|
||||||
|
settings.update(kwargs)
|
||||||
|
for k, v in settings.iteritems():
|
||||||
|
if v is None:
|
||||||
|
cmd.append('{}='.format(k))
|
||||||
|
else:
|
||||||
|
cmd.append('{}={}'.format(k, v))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
@ -90,7 +90,7 @@ def service_available(service_name):
|
|||||||
['service', service_name, 'status'],
|
['service', service_name, 'status'],
|
||||||
stderr=subprocess.STDOUT).decode('UTF-8')
|
stderr=subprocess.STDOUT).decode('UTF-8')
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
return 'unrecognized service' not in e.output
|
return b'unrecognized service' not in e.output
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import json
|
import json
|
||||||
from collections import Iterable
|
from inspect import getargspec
|
||||||
|
from collections import Iterable, OrderedDict
|
||||||
|
|
||||||
from charmhelpers.core import host
|
from charmhelpers.core import host
|
||||||
from charmhelpers.core import hookenv
|
from charmhelpers.core import hookenv
|
||||||
@ -119,7 +119,7 @@ class ServiceManager(object):
|
|||||||
"""
|
"""
|
||||||
self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json')
|
self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json')
|
||||||
self._ready = None
|
self._ready = None
|
||||||
self.services = {}
|
self.services = OrderedDict()
|
||||||
for service in services or []:
|
for service in services or []:
|
||||||
service_name = service['service']
|
service_name = service['service']
|
||||||
self.services[service_name] = service
|
self.services[service_name] = service
|
||||||
@ -132,8 +132,8 @@ class ServiceManager(object):
|
|||||||
if hook_name == 'stop':
|
if hook_name == 'stop':
|
||||||
self.stop_services()
|
self.stop_services()
|
||||||
else:
|
else:
|
||||||
self.provide_data()
|
|
||||||
self.reconfigure_services()
|
self.reconfigure_services()
|
||||||
|
self.provide_data()
|
||||||
cfg = hookenv.config()
|
cfg = hookenv.config()
|
||||||
if cfg.implicit_save:
|
if cfg.implicit_save:
|
||||||
cfg.save()
|
cfg.save()
|
||||||
@ -145,15 +145,36 @@ class ServiceManager(object):
|
|||||||
A provider must have a `name` attribute, which indicates which relation
|
A provider must have a `name` attribute, which indicates which relation
|
||||||
to set data on, and a `provide_data()` method, which returns a dict of
|
to set data on, and a `provide_data()` method, which returns a dict of
|
||||||
data to set.
|
data to set.
|
||||||
|
|
||||||
|
The `provide_data()` method can optionally accept two parameters:
|
||||||
|
|
||||||
|
* ``remote_service`` The name of the remote service that the data will
|
||||||
|
be provided to. The `provide_data()` method will be called once
|
||||||
|
for each connected service (not unit). This allows the method to
|
||||||
|
tailor its data to the given service.
|
||||||
|
* ``service_ready`` Whether or not the service definition had all of
|
||||||
|
its requirements met, and thus the ``data_ready`` callbacks run.
|
||||||
|
|
||||||
|
Note that the ``provided_data`` methods are now called **after** the
|
||||||
|
``data_ready`` callbacks are run. This gives the ``data_ready`` callbacks
|
||||||
|
a chance to generate any data necessary for the providing to the remote
|
||||||
|
services.
|
||||||
"""
|
"""
|
||||||
hook_name = hookenv.hook_name()
|
for service_name, service in self.services.items():
|
||||||
for service in self.services.values():
|
service_ready = self.is_ready(service_name)
|
||||||
for provider in service.get('provided_data', []):
|
for provider in service.get('provided_data', []):
|
||||||
if re.match(r'{}-relation-(joined|changed)'.format(provider.name), hook_name):
|
for relid in hookenv.relation_ids(provider.name):
|
||||||
data = provider.provide_data()
|
units = hookenv.related_units(relid)
|
||||||
_ready = provider._is_ready(data) if hasattr(provider, '_is_ready') else data
|
if not units:
|
||||||
if _ready:
|
continue
|
||||||
hookenv.relation_set(None, data)
|
remote_service = units[0].split('/')[0]
|
||||||
|
argspec = getargspec(provider.provide_data)
|
||||||
|
if len(argspec.args) > 1:
|
||||||
|
data = provider.provide_data(remote_service, service_ready)
|
||||||
|
else:
|
||||||
|
data = provider.provide_data()
|
||||||
|
if data:
|
||||||
|
hookenv.relation_set(relid, data)
|
||||||
|
|
||||||
def reconfigure_services(self, *service_names):
|
def reconfigure_services(self, *service_names):
|
||||||
"""
|
"""
|
||||||
|
@ -158,7 +158,7 @@ def filter_installed_packages(packages):
|
|||||||
|
|
||||||
def apt_cache(in_memory=True):
|
def apt_cache(in_memory=True):
|
||||||
"""Build and return an apt cache"""
|
"""Build and return an apt cache"""
|
||||||
import apt_pkg
|
from apt import apt_pkg
|
||||||
apt_pkg.init()
|
apt_pkg.init()
|
||||||
if in_memory:
|
if in_memory:
|
||||||
apt_pkg.config.set("Dir::Cache::pkgcache", "")
|
apt_pkg.config.set("Dir::Cache::pkgcache", "")
|
||||||
|
@ -45,14 +45,16 @@ class GitUrlFetchHandler(BaseFetchHandler):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def clone(self, source, dest, branch):
|
def clone(self, source, dest, branch, depth=None):
|
||||||
if not self.can_handle(source):
|
if not self.can_handle(source):
|
||||||
raise UnhandledSource("Cannot handle {}".format(source))
|
raise UnhandledSource("Cannot handle {}".format(source))
|
||||||
|
|
||||||
repo = Repo.clone_from(source, dest)
|
if depth:
|
||||||
repo.git.checkout(branch)
|
Repo.clone_from(source, dest, branch=branch, depth=depth)
|
||||||
|
else:
|
||||||
|
Repo.clone_from(source, dest, branch=branch)
|
||||||
|
|
||||||
def install(self, source, branch="master", dest=None):
|
def install(self, source, branch="master", dest=None, depth=None):
|
||||||
url_parts = self.parse_url(source)
|
url_parts = self.parse_url(source)
|
||||||
branch_name = url_parts.path.strip("/").split("/")[-1]
|
branch_name = url_parts.path.strip("/").split("/")[-1]
|
||||||
if dest:
|
if dest:
|
||||||
@ -63,7 +65,7 @@ class GitUrlFetchHandler(BaseFetchHandler):
|
|||||||
if not os.path.exists(dest_dir):
|
if not os.path.exists(dest_dir):
|
||||||
mkdir(dest_dir, perms=0o755)
|
mkdir(dest_dir, perms=0o755)
|
||||||
try:
|
try:
|
||||||
self.clone(source, dest_dir, branch)
|
self.clone(source, dest_dir, branch, depth)
|
||||||
except GitCommandError as e:
|
except GitCommandError as e:
|
||||||
raise UnhandledSource(e.message)
|
raise UnhandledSource(e.message)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
@ -14,6 +14,11 @@ from charmhelpers.contrib.openstack.utils import (
|
|||||||
os_release,
|
os_release,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
VLAN = 'vlan'
|
||||||
|
VXLAN = 'vxlan'
|
||||||
|
GRE = 'gre'
|
||||||
|
OVERLAY_NET_TYPES = [VXLAN, GRE]
|
||||||
|
|
||||||
|
|
||||||
def get_l2population():
|
def get_l2population():
|
||||||
plugin = config('neutron-plugin')
|
plugin = config('neutron-plugin')
|
||||||
@ -21,10 +26,12 @@ def get_l2population():
|
|||||||
|
|
||||||
|
|
||||||
def get_overlay_network_type():
|
def get_overlay_network_type():
|
||||||
overlay_net = config('overlay-network-type')
|
overlay_networks = config('overlay-network-type').split()
|
||||||
if overlay_net not in ['vxlan', 'gre']:
|
for overlay_net in overlay_networks:
|
||||||
raise Exception('Unsupported overlay-network-type')
|
if overlay_net not in OVERLAY_NET_TYPES:
|
||||||
return overlay_net
|
raise ValueError('Unsupported overlay-network-type %s'
|
||||||
|
% overlay_net)
|
||||||
|
return ','.join(overlay_networks)
|
||||||
|
|
||||||
|
|
||||||
def get_l3ha():
|
def get_l3ha():
|
||||||
@ -32,10 +39,6 @@ def get_l3ha():
|
|||||||
if os_release('neutron-server') < 'juno':
|
if os_release('neutron-server') < 'juno':
|
||||||
log('Disabling L3 HA, enable-l3ha is not valid before Juno')
|
log('Disabling L3 HA, enable-l3ha is not valid before Juno')
|
||||||
return False
|
return False
|
||||||
if config('overlay-network-type') not in ['vlan', 'gre', 'vxlan']:
|
|
||||||
log('Disabling L3 HA, enable-l3ha requires the use of the vxlan, '
|
|
||||||
'vlan or gre overlay network')
|
|
||||||
return False
|
|
||||||
if get_l2population():
|
if get_l2population():
|
||||||
log('Disabling L3 HA, l2-population must be disabled with L3 HA')
|
log('Disabling L3 HA, l2-population must be disabled with L3 HA')
|
||||||
return False
|
return False
|
||||||
@ -49,10 +52,11 @@ def get_dvr():
|
|||||||
if os_release('neutron-server') < 'juno':
|
if os_release('neutron-server') < 'juno':
|
||||||
log('Disabling DVR, enable-dvr is not valid before Juno')
|
log('Disabling DVR, enable-dvr is not valid before Juno')
|
||||||
return False
|
return False
|
||||||
if config('overlay-network-type') != 'vxlan':
|
if os_release('neutron-server') == 'juno':
|
||||||
log('Disabling DVR, enable-dvr requires the use of the vxlan '
|
if VXLAN not in config('overlay-network-type').split():
|
||||||
'overlay network')
|
log('Disabling DVR, enable-dvr requires the use of the vxlan '
|
||||||
return False
|
'overlay network for OpenStack Juno')
|
||||||
|
return False
|
||||||
if get_l3ha():
|
if get_l3ha():
|
||||||
log('Disabling DVR, enable-l3ha must be disabled with dvr')
|
log('Disabling DVR, enable-l3ha must be disabled with dvr')
|
||||||
return False
|
return False
|
||||||
|
@ -12,7 +12,7 @@ description: |
|
|||||||
etc.)
|
etc.)
|
||||||
.
|
.
|
||||||
This charm provides the OpenStack Neutron API service.
|
This charm provides the OpenStack Neutron API service.
|
||||||
categories:
|
tags:
|
||||||
- openstack
|
- openstack
|
||||||
provides:
|
provides:
|
||||||
nrpe-external-master:
|
nrpe-external-master:
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
type_drivers = local,flat
|
type_drivers = local,flat
|
||||||
mechanism_drivers = calico
|
mechanism_drivers = calico
|
||||||
{% else -%}
|
{% else -%}
|
||||||
type_drivers = gre,vxlan,vlan,flat
|
type_drivers = {{ overlay_network_type }},vlan,flat
|
||||||
tenant_network_types = gre,vxlan,vlan,flat
|
tenant_network_types = {{ overlay_network_type }},vlan,flat
|
||||||
mechanism_drivers = openvswitch,hyperv,l2population
|
mechanism_drivers = openvswitch,hyperv,l2population
|
||||||
|
|
||||||
[ml2_type_gre]
|
[ml2_type_gre]
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
[ml2]
|
[ml2]
|
||||||
type_drivers = gre,vxlan,vlan,flat
|
type_drivers = {{ overlay_network_type }},vlan,flat
|
||||||
tenant_network_types = gre,vxlan,vlan,flat
|
tenant_network_types = {{ overlay_network_type }},vlan,flat
|
||||||
mechanism_drivers = openvswitch,l2population
|
mechanism_drivers = openvswitch,l2population
|
||||||
|
|
||||||
[ml2_type_gre]
|
[ml2_type_gre]
|
||||||
|
@ -1,7 +1,43 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
"""
|
||||||
|
Basic neutron-api functional test.
|
||||||
|
|
||||||
|
test_* methods are called in sort order.
|
||||||
|
|
||||||
|
Convention to ensure desired test order:
|
||||||
|
1xx service and endpoint checks
|
||||||
|
2xx relation checks
|
||||||
|
3xx config checks
|
||||||
|
4xx functional checks
|
||||||
|
9xx restarts and other final checks
|
||||||
|
|
||||||
|
Common relation definitions:
|
||||||
|
- [ neutron-api, mysql ]
|
||||||
|
- [ neutron-api, rabbitmq-server ]
|
||||||
|
- [ neutron-api, nova-cloud-controller ]
|
||||||
|
- [ neutron-api, neutron-openvswitch ]
|
||||||
|
- [ neutron-api, keystone ]
|
||||||
|
- [ neutron-api, neutron-gateway ]
|
||||||
|
|
||||||
|
Resultant relations of neutron-api service:
|
||||||
|
relations:
|
||||||
|
amqp:
|
||||||
|
- rabbitmq-server
|
||||||
|
cluster:
|
||||||
|
- neutron-api
|
||||||
|
identity-service:
|
||||||
|
- keystone
|
||||||
|
neutron-api:
|
||||||
|
- nova-cloud-controller
|
||||||
|
neutron-plugin-api: # not inspected due to
|
||||||
|
- neutron-openvswitch # bug 1421388
|
||||||
|
shared-db:
|
||||||
|
- mysql
|
||||||
|
"""
|
||||||
|
|
||||||
import amulet
|
import amulet
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from charmhelpers.contrib.openstack.amulet.deployment import (
|
from charmhelpers.contrib.openstack.amulet.deployment import (
|
||||||
@ -76,10 +112,10 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
openstack_origin_git = {
|
openstack_origin_git = {
|
||||||
'repositories': [
|
'repositories': [
|
||||||
{'name': 'requirements',
|
{'name': 'requirements',
|
||||||
'repository': 'git://git.openstack.org/openstack/requirements',
|
'repository': 'git://github.com/openstack/requirements',
|
||||||
'branch': branch},
|
'branch': branch},
|
||||||
{'name': 'neutron',
|
{'name': 'neutron',
|
||||||
'repository': 'git://git.openstack.org/openstack/neutron',
|
'repository': 'git://github.com/openstack/neutron',
|
||||||
'branch': branch},
|
'branch': branch},
|
||||||
],
|
],
|
||||||
'directory': '/mnt/openstack-git',
|
'directory': '/mnt/openstack-git',
|
||||||
@ -110,9 +146,51 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
self._get_openstack_release()))
|
self._get_openstack_release()))
|
||||||
u.log.debug('openstack release str: {}'.format(
|
u.log.debug('openstack release str: {}'.format(
|
||||||
self._get_openstack_release_string()))
|
self._get_openstack_release_string()))
|
||||||
|
# Let things settle a bit before moving forward
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
def test_neutron_api_shared_db_relation(self):
|
|
||||||
|
def test_100_services(self):
|
||||||
|
"""Verify the expected services are running on the corresponding
|
||||||
|
service units."""
|
||||||
|
u.log.debug('Checking status of system services...')
|
||||||
|
# Fails vivid-kilo, bug 1454754
|
||||||
|
neutron_api_services = ['status neutron-server']
|
||||||
|
neutron_services = ['status neutron-dhcp-agent',
|
||||||
|
'status neutron-lbaas-agent',
|
||||||
|
'status neutron-metadata-agent',
|
||||||
|
'status neutron-plugin-openvswitch-agent',
|
||||||
|
'status neutron-ovs-cleanup']
|
||||||
|
|
||||||
|
if self._get_openstack_release() <= self.trusty_juno:
|
||||||
|
neutron_services.append('status neutron-vpn-agent')
|
||||||
|
|
||||||
|
if self._get_openstack_release() < self.trusty_kilo:
|
||||||
|
# Juno or earlier
|
||||||
|
neutron_services.append('status neutron-metering-agent')
|
||||||
|
|
||||||
|
nova_cc_services = ['status nova-api-ec2',
|
||||||
|
'status nova-api-os-compute',
|
||||||
|
'status nova-objectstore',
|
||||||
|
'status nova-cert',
|
||||||
|
'status nova-scheduler',
|
||||||
|
'status nova-conductor']
|
||||||
|
|
||||||
|
commands = {
|
||||||
|
self.mysql_sentry: ['status mysql'],
|
||||||
|
self.keystone_sentry: ['status keystone'],
|
||||||
|
self.nova_cc_sentry: nova_cc_services,
|
||||||
|
self.quantum_gateway_sentry: neutron_services,
|
||||||
|
self.neutron_api_sentry: neutron_api_services,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = u.validate_services(commands)
|
||||||
|
if ret:
|
||||||
|
amulet.raise_status(amulet.FAIL, msg=ret)
|
||||||
|
|
||||||
|
def test_200_neutron_api_shared_db_relation(self):
|
||||||
"""Verify the neutron-api to mysql shared-db relation data"""
|
"""Verify the neutron-api to mysql shared-db relation data"""
|
||||||
|
u.log.debug('Checking neutron-api:mysql relation data...')
|
||||||
unit = self.neutron_api_sentry
|
unit = self.neutron_api_sentry
|
||||||
relation = ['shared-db', 'mysql:shared-db']
|
relation = ['shared-db', 'mysql:shared-db']
|
||||||
expected = {
|
expected = {
|
||||||
@ -127,8 +205,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('neutron-api shared-db', ret)
|
message = u.relation_error('neutron-api shared-db', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_shared_db_neutron_api_relation(self):
|
def test_201_shared_db_neutron_api_relation(self):
|
||||||
"""Verify the mysql to neutron-api shared-db relation data"""
|
"""Verify the mysql to neutron-api shared-db relation data"""
|
||||||
|
u.log.debug('Checking mysql:neutron-api relation data...')
|
||||||
unit = self.mysql_sentry
|
unit = self.mysql_sentry
|
||||||
relation = ['shared-db', 'neutron-api:shared-db']
|
relation = ['shared-db', 'neutron-api:shared-db']
|
||||||
expected = {
|
expected = {
|
||||||
@ -149,8 +228,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('mysql shared-db', ret)
|
message = u.relation_error('mysql shared-db', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_neutron_api_amqp_relation(self):
|
def test_202_neutron_api_amqp_relation(self):
|
||||||
"""Verify the neutron-api to rabbitmq-server amqp relation data"""
|
"""Verify the neutron-api to rabbitmq-server amqp relation data"""
|
||||||
|
u.log.debug('Checking neutron-api:amqp relation data...')
|
||||||
unit = self.neutron_api_sentry
|
unit = self.neutron_api_sentry
|
||||||
relation = ['amqp', 'rabbitmq-server:amqp']
|
relation = ['amqp', 'rabbitmq-server:amqp']
|
||||||
expected = {
|
expected = {
|
||||||
@ -164,8 +244,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('neutron-api amqp', ret)
|
message = u.relation_error('neutron-api amqp', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_amqp_neutron_api_relation(self):
|
def test_203_amqp_neutron_api_relation(self):
|
||||||
"""Verify the rabbitmq-server to neutron-api amqp relation data"""
|
"""Verify the rabbitmq-server to neutron-api amqp relation data"""
|
||||||
|
u.log.debug('Checking amqp:neutron-api relation data...')
|
||||||
unit = self.rabbitmq_sentry
|
unit = self.rabbitmq_sentry
|
||||||
relation = ['amqp', 'neutron-api:amqp']
|
relation = ['amqp', 'neutron-api:amqp']
|
||||||
rel_data = unit.relation('amqp', 'neutron-api:amqp')
|
rel_data = unit.relation('amqp', 'neutron-api:amqp')
|
||||||
@ -179,8 +260,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('rabbitmq amqp', ret)
|
message = u.relation_error('rabbitmq amqp', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_neutron_api_identity_relation(self):
|
def test_204_neutron_api_identity_relation(self):
|
||||||
"""Verify the neutron-api to keystone identity-service relation data"""
|
"""Verify the neutron-api to keystone identity-service relation data"""
|
||||||
|
u.log.debug('Checking neutron-api:keystone relation data...')
|
||||||
unit = self.neutron_api_sentry
|
unit = self.neutron_api_sentry
|
||||||
relation = ['identity-service', 'keystone:identity-service']
|
relation = ['identity-service', 'keystone:identity-service']
|
||||||
api_ip = unit.relation('identity-service',
|
api_ip = unit.relation('identity-service',
|
||||||
@ -200,8 +282,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('neutron-api identity-service', ret)
|
message = u.relation_error('neutron-api identity-service', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_keystone_neutron_api_identity_relation(self):
|
def test_205_keystone_neutron_api_identity_relation(self):
|
||||||
"""Verify the keystone to neutron-api identity-service relation data"""
|
"""Verify the keystone to neutron-api identity-service relation data"""
|
||||||
|
u.log.debug('Checking keystone:neutron-api relation data...')
|
||||||
unit = self.keystone_sentry
|
unit = self.keystone_sentry
|
||||||
relation = ['identity-service', 'neutron-api:identity-service']
|
relation = ['identity-service', 'neutron-api:identity-service']
|
||||||
id_relation = unit.relation('identity-service',
|
id_relation = unit.relation('identity-service',
|
||||||
@ -220,8 +303,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('neutron-api identity-service', ret)
|
message = u.relation_error('neutron-api identity-service', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_neutron_api_plugin_relation(self):
|
def test_206_neutron_api_plugin_relation(self):
|
||||||
"""Verify neutron-api to neutron-openvswitch neutron-plugin-api"""
|
"""Verify neutron-api to neutron-openvswitch neutron-plugin-api"""
|
||||||
|
u.log.debug('Checking neutron-api:neutron-ovs relation data...')
|
||||||
unit = self.neutron_api_sentry
|
unit = self.neutron_api_sentry
|
||||||
relation = ['neutron-plugin-api',
|
relation = ['neutron-plugin-api',
|
||||||
'neutron-openvswitch:neutron-plugin-api']
|
'neutron-openvswitch:neutron-plugin-api']
|
||||||
@ -233,33 +317,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('neutron-api neutron-plugin-api', ret)
|
message = u.relation_error('neutron-api neutron-plugin-api', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
# XXX Test missing to examine the relation data neutron-openvswitch is
|
def test_207_neutron_api_novacc_relation(self):
|
||||||
# receiving. Current;y this data cannot be interegated due to
|
|
||||||
# Bug#1421388
|
|
||||||
|
|
||||||
def test_z_restart_on_config_change(self):
|
|
||||||
"""Verify that the specified services are restarted when the config
|
|
||||||
is changed.
|
|
||||||
|
|
||||||
Note(coreycb): The method name with the _z_ is a little odd
|
|
||||||
but it forces the test to run last. It just makes things
|
|
||||||
easier because restarting services requires re-authorization.
|
|
||||||
"""
|
|
||||||
conf = '/etc/neutron/neutron.conf'
|
|
||||||
services = ['neutron-server']
|
|
||||||
self.d.configure('neutron-api', {'use-syslog': 'True'})
|
|
||||||
stime = 60
|
|
||||||
for s in services:
|
|
||||||
if not u.service_restarted(self.neutron_api_sentry, s, conf,
|
|
||||||
pgrep_full=True, sleep_time=stime):
|
|
||||||
self.d.configure('neutron-api', {'use-syslog': 'False'})
|
|
||||||
msg = "service {} didn't restart after config change".format(s)
|
|
||||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
|
||||||
stime = 0
|
|
||||||
self.d.configure('neutron-api', {'use-syslog': 'False'})
|
|
||||||
|
|
||||||
def test_neutron_api_novacc_relation(self):
|
|
||||||
"""Verify the neutron-api to nova-cloud-controller relation data"""
|
"""Verify the neutron-api to nova-cloud-controller relation data"""
|
||||||
|
u.log.debug('Checking neutron-api:novacc relation data...')
|
||||||
unit = self.neutron_api_sentry
|
unit = self.neutron_api_sentry
|
||||||
relation = ['neutron-api', 'nova-cloud-controller:neutron-api']
|
relation = ['neutron-api', 'nova-cloud-controller:neutron-api']
|
||||||
api_ip = unit.relation('identity-service',
|
api_ip = unit.relation('identity-service',
|
||||||
@ -276,8 +336,9 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('neutron-api neutron-api', ret)
|
message = u.relation_error('neutron-api neutron-api', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_novacc_neutron_api_relation(self):
|
def test_208_novacc_neutron_api_relation(self):
|
||||||
"""Verify the nova-cloud-controller to neutron-api relation data"""
|
"""Verify the nova-cloud-controller to neutron-api relation data"""
|
||||||
|
u.log.debug('Checking novacc:neutron-api relation data...')
|
||||||
unit = self.nova_cc_sentry
|
unit = self.nova_cc_sentry
|
||||||
relation = ['neutron-api', 'neutron-api:neutron-api']
|
relation = ['neutron-api', 'neutron-api:neutron-api']
|
||||||
cc_ip = unit.relation('neutron-api',
|
cc_ip = unit.relation('neutron-api',
|
||||||
@ -292,8 +353,37 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = u.relation_error('nova-cc neutron-api', ret)
|
message = u.relation_error('nova-cc neutron-api', ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_neutron_config(self):
|
# XXX Test missing to examine the relation data neutron-openvswitch is
|
||||||
|
# receiving. Current;y this data cannot be interegated due to
|
||||||
|
# Bug#1421388
|
||||||
|
|
||||||
|
def test_900_restart_on_config_change(self):
|
||||||
|
"""Verify that the specified services are restarted when the config
|
||||||
|
is changed.
|
||||||
|
|
||||||
|
Note(coreycb): The method name with the _z_ is a little odd
|
||||||
|
but it forces the test to run last. It just makes things
|
||||||
|
easier because restarting services requires re-authorization.
|
||||||
|
"""
|
||||||
|
u.log.debug('Checking novacc neutron-api relation data...')
|
||||||
|
conf = '/etc/neutron/neutron.conf'
|
||||||
|
services = ['neutron-server']
|
||||||
|
u.log.debug('Making config change on neutron-api service...')
|
||||||
|
self.d.configure('neutron-api', {'use-syslog': 'True'})
|
||||||
|
stime = 60
|
||||||
|
for s in services:
|
||||||
|
u.log.debug("Checking that service restarted: {}".format(s))
|
||||||
|
if not u.service_restarted(self.neutron_api_sentry, s, conf,
|
||||||
|
pgrep_full=True, sleep_time=stime):
|
||||||
|
self.d.configure('neutron-api', {'use-syslog': 'False'})
|
||||||
|
msg = "service {} didn't restart after config change".format(s)
|
||||||
|
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||||
|
stime = 0
|
||||||
|
self.d.configure('neutron-api', {'use-syslog': 'False'})
|
||||||
|
|
||||||
|
def test_300_neutron_config(self):
|
||||||
"""Verify the data in the neutron config file."""
|
"""Verify the data in the neutron config file."""
|
||||||
|
u.log.debug('Checking neutron.conf config file data...')
|
||||||
unit = self.neutron_api_sentry
|
unit = self.neutron_api_sentry
|
||||||
cc_relation = self.nova_cc_sentry.relation('neutron-api',
|
cc_relation = self.nova_cc_sentry.relation('neutron-api',
|
||||||
'neutron-api:neutron-api')
|
'neutron-api:neutron-api')
|
||||||
@ -372,17 +462,18 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
message = "neutron config error: {}".format(ret)
|
message = "neutron config error: {}".format(ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_ml2_config(self):
|
def test_301_ml2_config(self):
|
||||||
"""Verify the data in the ml2 config file. This is only available
|
"""Verify the data in the ml2 config file. This is only available
|
||||||
since icehouse."""
|
since icehouse."""
|
||||||
|
u.log.debug('Checking ml2 config file data...')
|
||||||
unit = self.neutron_api_sentry
|
unit = self.neutron_api_sentry
|
||||||
conf = '/etc/neutron/plugins/ml2/ml2_conf.ini'
|
conf = '/etc/neutron/plugins/ml2/ml2_conf.ini'
|
||||||
neutron_api_relation = unit.relation('shared-db', 'mysql:shared-db')
|
neutron_api_relation = unit.relation('shared-db', 'mysql:shared-db')
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'ml2': {
|
'ml2': {
|
||||||
'type_drivers': 'gre,vxlan,vlan,flat',
|
'type_drivers': 'gre,vlan,flat',
|
||||||
'tenant_network_types': 'gre,vxlan,vlan,flat',
|
'tenant_network_types': 'gre,vlan,flat',
|
||||||
},
|
},
|
||||||
'ml2_type_gre': {
|
'ml2_type_gre': {
|
||||||
'tunnel_id_ranges': '1:1000'
|
'tunnel_id_ranges': '1:1000'
|
||||||
@ -422,39 +513,3 @@ class NeutronAPIBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
if ret:
|
if ret:
|
||||||
message = "ml2 config error: {}".format(ret)
|
message = "ml2 config error: {}".format(ret)
|
||||||
amulet.raise_status(amulet.FAIL, msg=message)
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
|
||||||
def test_services(self):
|
|
||||||
"""Verify the expected services are running on the corresponding
|
|
||||||
service units."""
|
|
||||||
neutron_api_services = ['status neutron-server']
|
|
||||||
neutron_services = ['status neutron-dhcp-agent',
|
|
||||||
'status neutron-lbaas-agent',
|
|
||||||
'status neutron-metadata-agent',
|
|
||||||
'status neutron-plugin-openvswitch-agent',
|
|
||||||
'status neutron-ovs-cleanup']
|
|
||||||
|
|
||||||
if self._get_openstack_release() <= self.trusty_juno:
|
|
||||||
neutron_services.append('status neutron-vpn-agent')
|
|
||||||
|
|
||||||
if self._get_openstack_release() < self.trusty_kilo:
|
|
||||||
# Juno or earlier
|
|
||||||
neutron_services.append('status neutron-metering-agent')
|
|
||||||
|
|
||||||
nova_cc_services = ['status nova-api-ec2',
|
|
||||||
'status nova-api-os-compute',
|
|
||||||
'status nova-objectstore',
|
|
||||||
'status nova-cert',
|
|
||||||
'status nova-scheduler',
|
|
||||||
'status nova-conductor']
|
|
||||||
|
|
||||||
commands = {
|
|
||||||
self.mysql_sentry: ['status mysql'],
|
|
||||||
self.keystone_sentry: ['status keystone'],
|
|
||||||
self.nova_cc_sentry: nova_cc_services,
|
|
||||||
self.quantum_gateway_sentry: neutron_services,
|
|
||||||
self.neutron_api_sentry: neutron_api_services,
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = u.validate_services(commands)
|
|
||||||
if ret:
|
|
||||||
amulet.raise_status(amulet.FAIL, msg=ret)
|
|
||||||
|
@ -89,7 +89,11 @@ class AmuletUtils(object):
|
|||||||
def _get_config(self, unit, filename):
|
def _get_config(self, unit, filename):
|
||||||
"""Get a ConfigParser object for parsing a unit's config file."""
|
"""Get a ConfigParser object for parsing a unit's config file."""
|
||||||
file_contents = unit.file_contents(filename)
|
file_contents = unit.file_contents(filename)
|
||||||
config = ConfigParser.ConfigParser()
|
|
||||||
|
# NOTE(beisner): by default, ConfigParser does not handle options
|
||||||
|
# with no value, such as the flags used in the mysql my.cnf file.
|
||||||
|
# https://bugs.python.org/issue7005
|
||||||
|
config = ConfigParser.ConfigParser(allow_no_value=True)
|
||||||
config.readfp(io.StringIO(file_contents))
|
config.readfp(io.StringIO(file_contents))
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -34,12 +34,16 @@ class GeneralTests(CharmTestCase):
|
|||||||
self.test_config.set('overlay-network-type', 'gre')
|
self.test_config.set('overlay-network-type', 'gre')
|
||||||
self.assertEquals(context.get_overlay_network_type(), 'gre')
|
self.assertEquals(context.get_overlay_network_type(), 'gre')
|
||||||
|
|
||||||
|
def test_get_overlay_network_type_multi(self):
|
||||||
|
self.test_config.set('overlay-network-type', 'gre vxlan')
|
||||||
|
self.assertEquals(context.get_overlay_network_type(), 'gre,vxlan')
|
||||||
|
|
||||||
def test_get_overlay_network_type_unsupported(self):
|
def test_get_overlay_network_type_unsupported(self):
|
||||||
self.test_config.set('overlay-network-type', 'tokenring')
|
self.test_config.set('overlay-network-type', 'tokenring')
|
||||||
with self.assertRaises(Exception) as _exceptctxt:
|
with self.assertRaises(ValueError) as _exceptctxt:
|
||||||
context.get_overlay_network_type()
|
context.get_overlay_network_type()
|
||||||
self.assertEqual(_exceptctxt.exception.message,
|
self.assertEqual(_exceptctxt.exception.message,
|
||||||
'Unsupported overlay-network-type')
|
'Unsupported overlay-network-type tokenring')
|
||||||
|
|
||||||
def test_get_l3ha(self):
|
def test_get_l3ha(self):
|
||||||
self.test_config.set('enable-l3ha', True)
|
self.test_config.set('enable-l3ha', True)
|
||||||
@ -65,14 +69,6 @@ class GeneralTests(CharmTestCase):
|
|||||||
self.os_release.return_value = 'juno'
|
self.os_release.return_value = 'juno'
|
||||||
self.assertEquals(context.get_l3ha(), False)
|
self.assertEquals(context.get_l3ha(), False)
|
||||||
|
|
||||||
def test_get_l3ha_badoverlay(self):
|
|
||||||
self.test_config.set('enable-l3ha', True)
|
|
||||||
self.test_config.set('overlay-network-type', 'tokenring')
|
|
||||||
self.test_config.set('neutron-plugin', 'ovs')
|
|
||||||
self.test_config.set('l2-population', False)
|
|
||||||
self.os_release.return_value = 'juno'
|
|
||||||
self.assertEquals(context.get_l3ha(), False)
|
|
||||||
|
|
||||||
def test_get_dvr(self):
|
def test_get_dvr(self):
|
||||||
self.test_config.set('enable-dvr', True)
|
self.test_config.set('enable-dvr', True)
|
||||||
self.test_config.set('enable-l3ha', False)
|
self.test_config.set('enable-l3ha', False)
|
||||||
@ -109,6 +105,24 @@ class GeneralTests(CharmTestCase):
|
|||||||
self.os_release.return_value = 'juno'
|
self.os_release.return_value = 'juno'
|
||||||
self.assertEquals(context.get_dvr(), False)
|
self.assertEquals(context.get_dvr(), False)
|
||||||
|
|
||||||
|
def test_get_dvr_gre_kilo(self):
|
||||||
|
self.test_config.set('enable-dvr', True)
|
||||||
|
self.test_config.set('enable-l3ha', False)
|
||||||
|
self.test_config.set('overlay-network-type', 'gre')
|
||||||
|
self.test_config.set('neutron-plugin', 'ovs')
|
||||||
|
self.test_config.set('l2-population', True)
|
||||||
|
self.os_release.return_value = 'kilo'
|
||||||
|
self.assertEquals(context.get_dvr(), True)
|
||||||
|
|
||||||
|
def test_get_dvr_vxlan_kilo(self):
|
||||||
|
self.test_config.set('enable-dvr', True)
|
||||||
|
self.test_config.set('enable-l3ha', False)
|
||||||
|
self.test_config.set('overlay-network-type', 'vxlan')
|
||||||
|
self.test_config.set('neutron-plugin', 'ovs')
|
||||||
|
self.test_config.set('l2-population', True)
|
||||||
|
self.os_release.return_value = 'kilo'
|
||||||
|
self.assertEquals(context.get_dvr(), True)
|
||||||
|
|
||||||
def test_get_dvr_l3ha_on(self):
|
def test_get_dvr_l3ha_on(self):
|
||||||
self.test_config.set('enable-dvr', True)
|
self.test_config.set('enable-dvr', True)
|
||||||
self.test_config.set('enable-l3ha', True)
|
self.test_config.set('enable-l3ha', True)
|
||||||
|
Loading…
Reference in New Issue
Block a user