First commit of charm-manila-generic
The generic manila charm provides the generic backend NFS configuration for the manila file service charm (charm-manila). This is the first commit of code, and the CI tests have been disabled to enable it to land. This is because of a circular dependency with the manila charm which requires this charm to be able to configure at least one backend. This patchset is dependent on the interface-manila-plugin interface and an updated version of charms.openstack that provides the 'options' member: these are declared below. Change-Id: I052f272dcd310091d988afd7104dea68115053ac Depends-On: Ied0ad014ab7b1d4778113b0d3f2bbae08075372e Depends-On: If6d103b4f62c95b0fa76562a18e418e0d319e987
This commit is contained in:
parent
1142d53f3b
commit
9b039d3981
|
@ -0,0 +1,7 @@
|
||||||
|
build
|
||||||
|
.tox
|
||||||
|
layers
|
||||||
|
interfaces
|
||||||
|
trusty
|
||||||
|
.testrepository
|
||||||
|
__pycache__
|
|
@ -0,0 +1,8 @@
|
||||||
|
[DEFAULT]
|
||||||
|
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||||
|
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||||
|
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||||
|
${PYTHON:-python} -m subunit.run discover -t ./ ./unit_tests $LISTOPT $IDOPTION
|
||||||
|
|
||||||
|
test_id_option=--load-list $IDFILE
|
||||||
|
test_list_option=--list
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This charm is developed as part of the OpenStack Charms project, and as such you
|
||||||
|
should refer to the [OpenStack Charm Development Guide](https://github.com/openstack/charm-guide) for details on how
|
||||||
|
to contribute to this charm.
|
||||||
|
|
||||||
|
You can find its source code here: <https://github.com/openstack/charm-manila-generic>.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/make
|
||||||
|
|
||||||
|
clean:
|
||||||
|
find . -iname '*.pyc' -delete
|
||||||
|
find . -iname '__pycache__' -delete
|
||||||
|
|
||||||
|
default:
|
||||||
|
echo "Doing nothing -- run 'make clean'"
|
||||||
|
|
||||||
|
all: default
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Manila Generic Backend Source Charm
|
||||||
|
|
||||||
|
THIS CHARM IS FOR EXPERIMENTAL USE AT PRESENT. This is a new charm for the
|
||||||
|
Manila service and it provides the generic backend plugin configuration.
|
||||||
|
|
||||||
|
This repository is for the reactive, layered, _source_ charm.
|
||||||
|
|
||||||
|
Please see the src/README.md for details on the built Manila-generic backend
|
||||||
|
charm and how to use it.
|
||||||
|
|
||||||
|
## Building the charm
|
||||||
|
|
||||||
|
To build the charm run the following command in the root of the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ tox -e build
|
||||||
|
```
|
||||||
|
|
||||||
|
The resultant built charm will be in the builds directory.
|
|
@ -0,0 +1,57 @@
|
||||||
|
TODO
|
||||||
|
====
|
||||||
|
|
||||||
|
* Add roles to the manila charm: api, scheduler, data, process, (all)
|
||||||
|
* Add a manila-backend-plugin interface
|
||||||
|
* Split the generic configuration into manila-generic-backend charm
|
||||||
|
* Add unit tests
|
||||||
|
* Add amulet tests
|
||||||
|
* Put the manual testing bits into charm-openstack-testing so that the bundles
|
||||||
|
are available
|
||||||
|
|
||||||
|
## Add roles:
|
||||||
|
|
||||||
|
It's necessary for the manila charm to be able to install itself as one of a
|
||||||
|
number of roles:
|
||||||
|
|
||||||
|
1. The manila-api: this provides the API to the rest of OpenStack. Until this
|
||||||
|
is HA aware, only ONE manila-api can be provisioned. Also, it may not make
|
||||||
|
sense to provision more than one manila-api server per OpenStack
|
||||||
|
installation.
|
||||||
|
2. The manila-scheduler: TODO
|
||||||
|
3. The manila-data process: TODO
|
||||||
|
4. The manila-share process: TODO
|
||||||
|
|
||||||
|
|
||||||
|
## Split the generic backend configuration out into a separate charm + interface
|
||||||
|
|
||||||
|
It's necessary to have the ability to configure a share backend independently
|
||||||
|
of the main charm. This means that plugin charms will be used to configure
|
||||||
|
each backend.
|
||||||
|
|
||||||
|
Essentially, a plugin needs to be able to configure:
|
||||||
|
|
||||||
|
- it's section in the manila.conf along with any network plugin's that it
|
||||||
|
needs (assuming that it's a share that manages it's own share-instance).
|
||||||
|
- ensure that the relevant bits are restarted.
|
||||||
|
|
||||||
|
It's not clear whether, for example, the api bit needs to know if the backend
|
||||||
|
is a generic backend, rather than something else.
|
||||||
|
|
||||||
|
Anyway, to start with:
|
||||||
|
|
||||||
|
- charm-manila : the main charm that can be deployed as multiple roles
|
||||||
|
- interface-manila-backend-plugin : the interface for plugging in the generic
|
||||||
|
backend (and other interfaces)
|
||||||
|
- charm-manila-generic-backend : the plugin for configuring the generic backend.
|
||||||
|
|
||||||
|
The backend needs to provide a piece of the manila.conf configuration file with
|
||||||
|
the bits necessary to configure the backend. This is mostly for the share,
|
||||||
|
rather than the api level. However, the issue is that parts of this file
|
||||||
|
actually need informatation from the principal charm (i.e. the manila service
|
||||||
|
user and password). And only the API charm should register with keystone
|
||||||
|
(particularly when the HA stuff is done with a floating VIP).
|
||||||
|
|
||||||
|
So, to solve that particular problem, we need to 'half' do the template, OR
|
||||||
|
provide the keystone 'manila' user credentials across the interface. And I
|
||||||
|
prefer the latter!
|
|
@ -0,0 +1,3 @@
|
||||||
|
#charm-tools
|
||||||
|
git+https://github.com/juju/charm-tools#egg=charm-tools
|
||||||
|
simplejson
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This is a _pre-release_ charm intended for *testing* only.
|
||||||
|
|
||||||
|
This charm configures the generic backend in the related manila charm in an
|
||||||
|
OpenStack cloud. This provides NFS shares using Cinder as a backing store. It
|
||||||
|
should be used for testing and development purposes only.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
The charm relies on the prinical manila charm, and is a subordinate to it. It
|
||||||
|
provides configuration data to the manila-share service (which is provided by
|
||||||
|
the manila charm with a role that includes 'share').
|
||||||
|
|
||||||
|
If multiple, _different_, generic backend configurations are required then the
|
||||||
|
`share-backend-name` config option should be used to differentiate between the
|
||||||
|
configuration sections.
|
||||||
|
|
||||||
|
_Note_: this subordinate charm requests that manila configure the nova, neutron
|
||||||
|
and cinder sections that the generic driver needs to launch NFS share instances
|
||||||
|
that provide NFS/CIFS services within their tenant networks. The manila charm
|
||||||
|
provides the _main_ manila service username/password to this charm to enable it
|
||||||
|
to provide those configuration sections.
|
||||||
|
|
||||||
|
# Bugs
|
||||||
|
|
||||||
|
Please report bugs on [Launchpad](https://bugs.launchpad.net/charm-manila-generic/+filebug).
|
||||||
|
|
||||||
|
For general questions please refer to the OpenStack [Charm Guide](https://github.com/openstack/charm-guide).
|
|
@ -0,0 +1,96 @@
|
||||||
|
options:
|
||||||
|
openstack-origin:
|
||||||
|
default: distro
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Repository from which to install. May be one of the following:
|
||||||
|
distro (default), ppa:somecustom/ppa, a deb url sources entry,
|
||||||
|
or a supported Cloud Archive release pocket.
|
||||||
|
|
||||||
|
Supported Cloud Archive sources include: cloud:precise-folsom,
|
||||||
|
cloud:precise-folsom/updates, cloud:precise-folsom/staging,
|
||||||
|
cloud:precise-folsom/proposed.
|
||||||
|
|
||||||
|
Note that updating this setting to a source that is known to
|
||||||
|
provide a later version of OpenStack will trigger a software
|
||||||
|
upgrade.
|
||||||
|
debug:
|
||||||
|
default: False
|
||||||
|
type: boolean
|
||||||
|
description: Enable debug logging
|
||||||
|
verbose:
|
||||||
|
default: False
|
||||||
|
type: boolean
|
||||||
|
description: Enable verbose logging
|
||||||
|
share-backend-name:
|
||||||
|
type: string
|
||||||
|
default: generic
|
||||||
|
description: |
|
||||||
|
The name given to the backend. This is used to generate the backend
|
||||||
|
configuration section and link it into the share server. If two
|
||||||
|
different configurations of the same backend type are needed, then this
|
||||||
|
config option can be used to separate them in the backend configuration.
|
||||||
|
share-protocols:
|
||||||
|
type: string
|
||||||
|
default: NFS CIFS
|
||||||
|
description: |
|
||||||
|
The share protocols that the backends will be able to provide. The
|
||||||
|
default is good for the generic backends. Other backends may not support
|
||||||
|
both NFS and CIFS. This is a space delimited list of protocols.
|
||||||
|
driver-service-image-name:
|
||||||
|
type: string
|
||||||
|
description: the image name to use for the generic instance
|
||||||
|
default: manila-service-image
|
||||||
|
driver-handles-share-servers:
|
||||||
|
type: boolean
|
||||||
|
description: Whether to generic driver should run up a share server.
|
||||||
|
default: True
|
||||||
|
driver-service-instance-flavor-id:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: |
|
||||||
|
The ID for the flavor to launch images in. The driver blocks if this is
|
||||||
|
not set.
|
||||||
|
driver-connect-share-server-to-tenant-network:
|
||||||
|
type: boolean
|
||||||
|
default: True
|
||||||
|
description: Whether to connect the share server into the tenant network.
|
||||||
|
driver-service-instance-user:
|
||||||
|
type: string
|
||||||
|
description: The user to log into the share instance.
|
||||||
|
default: manila
|
||||||
|
driver-auth-type:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
description: |
|
||||||
|
One of 'password', 'ssh', 'both'. This determines how manila
|
||||||
|
authenticates against the service-instance; e.g. using password, ssh
|
||||||
|
keypair or both.
|
||||||
|
driver-service-instance-password:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
description: |
|
||||||
|
If the service user doesn't log in with a key-pair a password is needed
|
||||||
|
to allow manila to ssh into the service instance. If the password is set
|
||||||
|
then it is used and an SSH key is not configured.
|
||||||
|
driver-service-ssh-key:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
description: |
|
||||||
|
The key for the manila to inject into the instance. If set, manila will
|
||||||
|
inject it into OpenStack if the keypair name doesn't exist.
|
||||||
|
driver-service-ssh-key-public:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
description: |
|
||||||
|
The public key for the manila to inject into the instance. If set,
|
||||||
|
manila will inject it into OpenStack if the keypair name doesn't exist.
|
||||||
|
driver-keypair-name:
|
||||||
|
type: string
|
||||||
|
default: manila-service
|
||||||
|
description: |
|
||||||
|
This is the keypair name that will be provided to nova instances. Note
|
||||||
|
that manila uploads the keypair from the config settings
|
||||||
|
'generic-driver-ssh-private-key' and 'generic-driver-ssh-public-key'. If
|
||||||
|
neither the ssh config vars are set nor the password then the charm will
|
||||||
|
block until they are set.
|
|
@ -0,0 +1,4 @@
|
||||||
|
includes:
|
||||||
|
- layer:openstack
|
||||||
|
- interface:manila-plugin
|
||||||
|
repo: https://github.com/openstack/charm-manila-generic
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
|
@ -0,0 +1,342 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
# The manila handlers class
|
||||||
|
|
||||||
|
# bare functions are provided to the reactive handlers to perform the functions
|
||||||
|
# needed on the class.
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
import charmhelpers.core.hookenv as hookenv
|
||||||
|
import charms_openstack.charm
|
||||||
|
import charms_openstack.adapters
|
||||||
|
|
||||||
|
# There are no additional packages to install.
|
||||||
|
PACKAGES = []
|
||||||
|
MANILA_DIR = '/etc/manila/'
|
||||||
|
MANILA_CONF = MANILA_DIR + "manila.conf"
|
||||||
|
|
||||||
|
MANILA_SSH_KEY_PATH = '/etc/manila/ssh_image_key'
|
||||||
|
MANILA_SSH_KEY_PATH_PUBLIC = '/etc/manila/ssh_image_key.pub'
|
||||||
|
|
||||||
|
# select the default release function and ssl feature
|
||||||
|
charms_openstack.charm.use_defaults('charm.default-select-release')
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Compute some options to help with template rendering
|
||||||
|
@charms_openstack.adapters.config_property
|
||||||
|
def computed_use_password(config):
|
||||||
|
"""Return True if the generic driver should use a password rather than an
|
||||||
|
ssh key.
|
||||||
|
:returns: boolean
|
||||||
|
"""
|
||||||
|
return (bool(config.driver_service_instance_password) &
|
||||||
|
((config.driver_auth_type or '').lower()
|
||||||
|
in ('password', 'both')))
|
||||||
|
|
||||||
|
|
||||||
|
@charms_openstack.adapters.config_property
|
||||||
|
def computed_use_ssh(config):
|
||||||
|
"""Return True if the generic driver should use a password rather than an
|
||||||
|
ssh key.
|
||||||
|
:returns: boolean
|
||||||
|
"""
|
||||||
|
return ((config.driver_auth_type or '').lower() in ('ssh', 'both'))
|
||||||
|
|
||||||
|
|
||||||
|
@charms_openstack.adapters.config_property
|
||||||
|
def computed_define_ssh(config):
|
||||||
|
"""Return True if the generic driver should define the SSH keys
|
||||||
|
:returns: boolean
|
||||||
|
"""
|
||||||
|
return (bool(config.driver_service_ssh_key) &
|
||||||
|
bool(config.driver_service_ssh_key_public))
|
||||||
|
|
||||||
|
|
||||||
|
@charms_openstack.adapters.config_property
|
||||||
|
def computed_debug_level(config):
|
||||||
|
"""Return NONE, INFO, WARNING, DEBUG depending on the settings of
|
||||||
|
options.debug and options.level
|
||||||
|
:returns: string, NONE, WARNING, DEBUG
|
||||||
|
"""
|
||||||
|
if not config.debug:
|
||||||
|
return "NONE"
|
||||||
|
if config.verbose:
|
||||||
|
return "DEBUG"
|
||||||
|
return "WARNING"
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Implementation of the Manila Charm classes
|
||||||
|
|
||||||
|
class ManilaGenericCharm(charms_openstack.charm.OpenStackCharm):
|
||||||
|
"""Generic backend driver configuration charm. This configures a nominally
|
||||||
|
named "generic" section along with nova, cinder and neutron sections to
|
||||||
|
enable the generic NFS driver in the front end.
|
||||||
|
"""
|
||||||
|
|
||||||
|
release = 'mitaka'
|
||||||
|
name = 'manila-generic'
|
||||||
|
packages = PACKAGES
|
||||||
|
version_package = 'manila-api' # need this for versioning the app
|
||||||
|
api_ports = {}
|
||||||
|
service_type = None
|
||||||
|
|
||||||
|
default_service = None # There is no service for this charm.
|
||||||
|
services = []
|
||||||
|
|
||||||
|
required_relations = []
|
||||||
|
|
||||||
|
restart_map = {}
|
||||||
|
|
||||||
|
# This is the command to sync the database
|
||||||
|
sync_cmd = []
|
||||||
|
|
||||||
|
def custom_assess_status_check(self):
|
||||||
|
"""Validate that the driver configuration is at least complete, and
|
||||||
|
that it was valid when it used (either at configuration time or config
|
||||||
|
changed time)
|
||||||
|
|
||||||
|
:returns (status: string, message: string): the status, and message if
|
||||||
|
there is a problem. Or (None, None) if there are no issues.
|
||||||
|
"""
|
||||||
|
options = self.options
|
||||||
|
if not options.driver_handles_share_servers:
|
||||||
|
# Nothing to check if the driver doesn't handle share servers
|
||||||
|
# directly.
|
||||||
|
return None, None
|
||||||
|
if not options.driver_service_image_name:
|
||||||
|
return 'blocked', "Missing 'driver-service-image-name'"
|
||||||
|
if not options.driver_service_instance_user:
|
||||||
|
return 'blocked', "Missing 'driver-service-instance-user'"
|
||||||
|
if not options.driver_service_instance_flavor_id:
|
||||||
|
return ('blocked',
|
||||||
|
"Missing 'driver-service-instance-flavor-id'")
|
||||||
|
# Need at least one of the password or the keypair
|
||||||
|
if not(bool(options.driver_service_instance_password) or
|
||||||
|
bool(options.driver_keypair_name)):
|
||||||
|
return ('blocked',
|
||||||
|
"Need at least one of instance password or keypair name")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def get_config_for_principal(self, auth_data):
|
||||||
|
"""Assuming that the configuration data is valid, return the
|
||||||
|
configuration data for the principal charm.
|
||||||
|
|
||||||
|
The format of the returned data is:
|
||||||
|
{
|
||||||
|
"complete": <boolean>,
|
||||||
|
'<config file>': {
|
||||||
|
'<section>: (
|
||||||
|
(key, value),
|
||||||
|
(key, value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
If the configuration is not complete, or we don't have auth data from
|
||||||
|
the principal charm, then we return:
|
||||||
|
{
|
||||||
|
"complete": false,
|
||||||
|
"reason": <message>
|
||||||
|
}
|
||||||
|
|
||||||
|
:param auth_data: the raw dictionary received from the principal charm
|
||||||
|
:returns: structure described above.
|
||||||
|
"""
|
||||||
|
if not auth_data:
|
||||||
|
return {"complete": False, "reason": "No authentication data"}
|
||||||
|
state, message = self.custom_assess_status_check()
|
||||||
|
if state:
|
||||||
|
return {"complete": False, "reason": message}
|
||||||
|
options = self.options # tiny optimisation for less typing.
|
||||||
|
# We have the auth data & the config is reasonably sensible.
|
||||||
|
if not options.share_backend_name:
|
||||||
|
return {"complete": False,
|
||||||
|
"reason": "Problem: share-backend-name is not set"}
|
||||||
|
|
||||||
|
# if the driver is not going to handle the share servers then we only
|
||||||
|
# need a very simple config section
|
||||||
|
if not options.driver_handles_share_servers:
|
||||||
|
generic_section = self.process_lines((
|
||||||
|
"# Set usage of Generic driver which uses cinder as backend.",
|
||||||
|
"share_driver = "
|
||||||
|
"manila.share.drivers.generic.GenericShareDriver",
|
||||||
|
"",
|
||||||
|
"# Generic driver supports both driver modes - "
|
||||||
|
"with and without handling",
|
||||||
|
"# of share servers. So, we need to define explicitly which "
|
||||||
|
"one we are",
|
||||||
|
"# enabling using this driver.",
|
||||||
|
"driver_handles_share_servers = False",
|
||||||
|
"# Custom name for share backend.",
|
||||||
|
("share_backend_name", options.share_backend_name),
|
||||||
|
"# Generic driver seems to insist on 'service_instance_user' "
|
||||||
|
"even if it isn't using it",
|
||||||
|
("service_instance_user",
|
||||||
|
options.driver_service_instance_user)))
|
||||||
|
return {
|
||||||
|
"complete": True,
|
||||||
|
MANILA_CONF: {
|
||||||
|
"[{}]".format(options.share_backend_name): generic_section,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# we use the same username/password/auth for each section as every
|
||||||
|
# service user has then same permissions as admin.
|
||||||
|
auth_section = self.process_lines((
|
||||||
|
"# Only needed for the generic drivers as of Mitaka",
|
||||||
|
('username', auth_data['username']),
|
||||||
|
('password', auth_data['password']),
|
||||||
|
('project_domain_id', auth_data['project_domain_id']),
|
||||||
|
('project_name', auth_data['project_name']),
|
||||||
|
('user_domain_id', auth_data['user_domain_id']),
|
||||||
|
('auth_uri', auth_data['auth_uri']),
|
||||||
|
('auth_url', auth_data['auth_url']),
|
||||||
|
('auth_type', auth_data['auth_type'])))
|
||||||
|
|
||||||
|
# Expression is True if the generic driver should use a password rather
|
||||||
|
# than an ssh key.
|
||||||
|
if options.computed_use_password:
|
||||||
|
service_instance_password = (
|
||||||
|
"service_instance_password",
|
||||||
|
options.driver_service_instance_password)
|
||||||
|
else:
|
||||||
|
service_instance_password = "# No generic password section"
|
||||||
|
|
||||||
|
# Expression is True if the generic driver should use a password rather
|
||||||
|
# than an ssh key.
|
||||||
|
if options.computed_use_ssh:
|
||||||
|
ssh_section = tuple(self.process_lines((
|
||||||
|
("path_to_private_key", MANILA_SSH_KEY_PATH),
|
||||||
|
("path_to_public_key", MANILA_SSH_KEY_PATH_PUBLIC),
|
||||||
|
("manila_service_keypair_name",
|
||||||
|
options.driver_keypair_name))))
|
||||||
|
else:
|
||||||
|
ssh_section = ("# No ssh section", )
|
||||||
|
|
||||||
|
# And finally configure the generic section
|
||||||
|
generic_section = self.process_lines((
|
||||||
|
"# Set usage of Generic driver which uses cinder as backend.",
|
||||||
|
"share_driver = manila.share.drivers.generic.GenericShareDriver",
|
||||||
|
"",
|
||||||
|
"# Generic driver supports both driver modes - "
|
||||||
|
"with and without handling",
|
||||||
|
"# of share servers. So, we need to define explicitly which one "
|
||||||
|
"we are",
|
||||||
|
"# enabling using this driver.",
|
||||||
|
("driver_handles_share_servers",
|
||||||
|
options.driver_handles_share_servers),
|
||||||
|
"",
|
||||||
|
"# The flavor that Manila will use to launch the instance.",
|
||||||
|
("service_instance_flavor_id",
|
||||||
|
options.driver_service_instance_flavor_id),
|
||||||
|
"",
|
||||||
|
"# Generic driver uses a glance image for building service VMs "
|
||||||
|
"in nova.",
|
||||||
|
"# The following options specify the image to use.",
|
||||||
|
"# We use the latest build of [1].",
|
||||||
|
"# [1] https://github.com/openstack/manila-image-elements",
|
||||||
|
("service_instance_user",
|
||||||
|
options.driver_service_instance_user),
|
||||||
|
("service_image_name", options.driver_service_image_name),
|
||||||
|
("connect_share_server_to_tenant_network",
|
||||||
|
options.driver_connect_share_server_to_tenant_network),
|
||||||
|
"",
|
||||||
|
"# These will be used for keypair creation and inserted into",
|
||||||
|
"# service VMs.",
|
||||||
|
"# TODO: this presents a problem with HA and failover - as the"
|
||||||
|
"keys",
|
||||||
|
"# will no longer be the same -- need to be able to set these via",
|
||||||
|
"# a config option.",
|
||||||
|
service_instance_password, ) +
|
||||||
|
ssh_section +
|
||||||
|
("",
|
||||||
|
"# Custom name for share backend.",
|
||||||
|
("share_backend_name", options.share_backend_name)))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"complete": True,
|
||||||
|
MANILA_CONF: {
|
||||||
|
"[nova]": auth_section,
|
||||||
|
"[neutron]": auth_section,
|
||||||
|
"[cinder]": auth_section,
|
||||||
|
"[{}]".format(options.share_backend_name): generic_section,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_lines(lines):
|
||||||
|
"""Process each of the lines. If the line is a string, then just
|
||||||
|
passes it though; if the line is a tuple (and it must be a 2-tuple)
|
||||||
|
then the string is interpolated with an equals.
|
||||||
|
|
||||||
|
:param lines: list of strings or 2-tuples of strings
|
||||||
|
:returns: list of strings
|
||||||
|
"""
|
||||||
|
out = []
|
||||||
|
for line in lines:
|
||||||
|
if isinstance(line, str):
|
||||||
|
out.append(line)
|
||||||
|
elif isinstance(line, (list, tuple)):
|
||||||
|
if len(line) != 2:
|
||||||
|
raise TypeError("Line '{}' must be length 2"
|
||||||
|
.format(line))
|
||||||
|
out.append("{} = {}".format(*line))
|
||||||
|
# raise an error on other types
|
||||||
|
else:
|
||||||
|
raise TypeError("Line '{}' must be a string, tuple or list."
|
||||||
|
" Passed a {}"
|
||||||
|
.format(line, type(line)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def maybe_write_ssh_keys(self):
|
||||||
|
"""Maybe write the ssh keys from the options to the key files where
|
||||||
|
manila will be able to find them. The function only writes them if the
|
||||||
|
configuration is to use the SSH config. If they are not to be written
|
||||||
|
and they exist then they are deleted.
|
||||||
|
"""
|
||||||
|
if (self.options.computed_use_ssh and
|
||||||
|
self.options.computed_define_ssh):
|
||||||
|
write_file(self.options.driver_service_ssh_key,
|
||||||
|
MANILA_SSH_KEY_PATH)
|
||||||
|
write_file(self.options.driver_service_ssh_key_public,
|
||||||
|
MANILA_SSH_KEY_PATH_PUBLIC, 0o644)
|
||||||
|
else:
|
||||||
|
for f in (MANILA_SSH_KEY_PATH, MANILA_SSH_KEY_PATH_PUBLIC):
|
||||||
|
try:
|
||||||
|
os.remove(f)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(contents, file, chown=0o600):
|
||||||
|
"""Write the contents to the file.
|
||||||
|
|
||||||
|
:param contents: the contents to write. This will be dedented, and striped
|
||||||
|
to ensure that it is just a set of lines.
|
||||||
|
:param file: the file to write
|
||||||
|
:param chown: the ownership for the file.
|
||||||
|
:raises OSError: If the file couldn't be written.
|
||||||
|
:returns None:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with os.fdopen(os.open(file,
|
||||||
|
os.O_WRONLY | os.O_CREAT,
|
||||||
|
chown), 'w') as f:
|
||||||
|
f.write(textwrap.dedent(contents))
|
||||||
|
except OSError as e:
|
||||||
|
hookenv.log("Couldn't write file: {}".format(str(e)))
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: manila-generic
|
||||||
|
summary: A generic backend configuration charm for manila.
|
||||||
|
maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
|
||||||
|
description: |
|
||||||
|
The Manil share file system service provides a set of services for management
|
||||||
|
of shared file systems in a multi-tenant cloud environment. The service
|
||||||
|
resembles OpenStack block-based storage management from the OpenStack Block
|
||||||
|
Storage service project. With the Shared File Systems service, you can create
|
||||||
|
a remote file system, mount the file system on your instances, and then read
|
||||||
|
and write data from your instances to and from your file system.
|
||||||
|
|
||||||
|
The manila-generic plugin (using the manila-plugin relation) provides the
|
||||||
|
configuration information to the manila charm to configure the Manila
|
||||||
|
instance such that it can use the generic driver appropriately.
|
||||||
|
tags:
|
||||||
|
- openstack
|
||||||
|
series:
|
||||||
|
- xenial
|
||||||
|
subordinate: true
|
||||||
|
provides:
|
||||||
|
manila-plugin:
|
||||||
|
interface: manila-plugin
|
||||||
|
scope: container
|
||||||
|
requires:
|
||||||
|
juju-info:
|
||||||
|
interface: juju-info
|
||||||
|
scope: container
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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 is just for the reactive handlers and calls into the charm.
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import charms.reactive
|
||||||
|
import charms_openstack.charm
|
||||||
|
|
||||||
|
# This charm's library contains all of the handler code associated with
|
||||||
|
# manila -- we need to import it to get the definitions for the charm.
|
||||||
|
import charm.openstack.manila_generic # noqa
|
||||||
|
|
||||||
|
|
||||||
|
# Use the charms.openstack defaults for common states and hooks
|
||||||
|
charms_openstack.charm.use_defaults(
|
||||||
|
'charm.installed',
|
||||||
|
'update-status')
|
||||||
|
|
||||||
|
|
||||||
|
@charms.reactive.when('manila-plugin.available')
|
||||||
|
@charms.reactive.when_not('config.changed')
|
||||||
|
def send_config(manila_plugin):
|
||||||
|
"""Send the configuration over to the prinicpal charm"""
|
||||||
|
with charms_openstack.charm.provide_charm_instance() as generic_charm:
|
||||||
|
# set the name of the backend using the configuration option
|
||||||
|
manila_plugin.name = generic_charm.options.share_backend_name
|
||||||
|
# Set the configuration data for the principal charm.
|
||||||
|
manila_plugin.configuration_data = (
|
||||||
|
generic_charm.get_config_for_principal(
|
||||||
|
manila_plugin.authentication_data))
|
||||||
|
generic_charm.maybe_write_ssh_keys()
|
||||||
|
generic_charm.assess_status()
|
||||||
|
|
||||||
|
|
||||||
|
@charms.reactive.when('manila-plugin.available',
|
||||||
|
'config.changed')
|
||||||
|
def update_config(manila_plugin):
|
||||||
|
send_config(manila_plugin)
|
|
@ -0,0 +1,22 @@
|
||||||
|
# charm-proof
|
||||||
|
charm-tools>=2.0.0
|
||||||
|
# amulet deployment helpers
|
||||||
|
bzr+lp:charm-helpers#egg=charmhelpers
|
||||||
|
# BEGIN: Amulet OpenStack Charm Helper Requirements
|
||||||
|
# Liberty client lower constraints
|
||||||
|
amulet>=1.14.3,<2.0
|
||||||
|
bundletester>=0.6.1,<1.0
|
||||||
|
python-keystoneclient>=1.7.1,<2.0
|
||||||
|
python-barbicanclient>=4.0.1,<5.0
|
||||||
|
python-designateclient>=1.5,<2.0
|
||||||
|
python-cinderclient>=1.4.0,<2.0
|
||||||
|
python-glanceclient>=1.1.0,<2.0
|
||||||
|
python-heatclient>=0.8.0,<1.0
|
||||||
|
python-neutronclient>=3.1.0,<4.0
|
||||||
|
python-novaclient>=2.30.1,<3.0
|
||||||
|
python-openstackclient>=1.7.0,<2.0
|
||||||
|
python-swiftclient>=2.6.0,<3.0
|
||||||
|
python-manilaclient>=1.8.1,<2.0
|
||||||
|
pika>=0.10.0,<1.0
|
||||||
|
distro-info
|
||||||
|
# END: Amulet OpenStack Charm Helper Requirements
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This directory provides Amulet tests to verify basic deployment functionality
|
||||||
|
from the perspective of this charm, its requirements and its features, as
|
||||||
|
exercised in a subset of the full OpenStack deployment test bundle topology.
|
||||||
|
|
||||||
|
For full details on functional testing of OpenStack charms please refer to
|
||||||
|
the [functional testing](http://docs.openstack.org/developer/charm-guide/testing.html#functional-testing)
|
||||||
|
section of the OpenStack Charm Guide.
|
|
@ -0,0 +1,326 @@
|
||||||
|
import amulet
|
||||||
|
|
||||||
|
from keystoneclient import session as keystone_session
|
||||||
|
from keystoneclient.auth import identity as keystone_identity
|
||||||
|
import keystoneclient.exceptions
|
||||||
|
from keystoneclient.v2_0 import client as keystone_v2_0_client
|
||||||
|
from keystoneclient.v3 import client as keystone_v3_client
|
||||||
|
from manilaclient.v1 import client as manila_client
|
||||||
|
|
||||||
|
from charmhelpers.contrib.openstack.amulet.deployment import (
|
||||||
|
OpenStackAmuletDeployment
|
||||||
|
)
|
||||||
|
|
||||||
|
from charmhelpers.contrib.openstack.amulet.utils import (
|
||||||
|
OpenStackAmuletUtils,
|
||||||
|
DEBUG,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use DEBUG to turn on debug logging
|
||||||
|
u = OpenStackAmuletUtils(DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
class ManilaGenericBasicDeployment(OpenStackAmuletDeployment):
|
||||||
|
"""Amulet tests on a basic Manila Generic deployment.
|
||||||
|
|
||||||
|
Note that these tests don't attempt to do a functional test on Manila,
|
||||||
|
merely to demonstrate that the relations work and that they transfer the
|
||||||
|
correct information across them. It verifies that the configuration goes
|
||||||
|
across to the manila main charm.
|
||||||
|
|
||||||
|
A functional test will be performed by a mojo or tempest test.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, series, openstack=None, source=None, stable=False,
|
||||||
|
keystone_version='2'):
|
||||||
|
"""Deploy the entire test environment.
|
||||||
|
"""
|
||||||
|
super(ManilaGenericBasicDeployment, self).__init__(
|
||||||
|
series, openstack, source, stable)
|
||||||
|
self._keystone_version = keystone_version
|
||||||
|
self._add_services()
|
||||||
|
self._add_relations()
|
||||||
|
self._configure_services()
|
||||||
|
self._deploy()
|
||||||
|
|
||||||
|
u.log.info('Waiting on extended status checks...')
|
||||||
|
exclude_services = ['mysql', ]
|
||||||
|
self._auto_wait_for_status(exclude_services=exclude_services)
|
||||||
|
|
||||||
|
self._initialize_tests()
|
||||||
|
|
||||||
|
def _add_services(self):
|
||||||
|
"""Add services
|
||||||
|
|
||||||
|
Add the services that we're testing, where manila is local,
|
||||||
|
and the rest of the service are from lp branches that are
|
||||||
|
compatible with the local charm (e.g. stable or next).
|
||||||
|
"""
|
||||||
|
this_service = {'name': 'manila-generic'}
|
||||||
|
other_services = [
|
||||||
|
{'name': 'mysql',
|
||||||
|
'location': 'cs:percona-cluster',
|
||||||
|
'constraints': {'mem': '3072M'}},
|
||||||
|
{'name': 'rabbitmq-server'},
|
||||||
|
{'name': 'keystone'},
|
||||||
|
{'name': 'manila',
|
||||||
|
'location': 'cs:~openstack-charmers/xenial/manila'}
|
||||||
|
]
|
||||||
|
super(ManilaGenericBasicDeployment, self)._add_services(
|
||||||
|
this_service, other_services)
|
||||||
|
|
||||||
|
def _add_relations(self):
|
||||||
|
"""Add all of the relations for the services."""
|
||||||
|
relations = {
|
||||||
|
'manila:shared-db': 'mysql:shared-db',
|
||||||
|
'manila:amqp': 'rabbitmq-server:amqp',
|
||||||
|
'manila:identity-service': 'keystone:identity-service',
|
||||||
|
'manila:manila-plugin': 'manila-generic:manila-plugin',
|
||||||
|
'keystone:shared-db': 'mysql:shared-db',
|
||||||
|
}
|
||||||
|
super(ManilaGenericBasicDeployment, self)._add_relations(relations)
|
||||||
|
|
||||||
|
def _configure_services(self):
|
||||||
|
"""Configure all of the services."""
|
||||||
|
keystone_config = {
|
||||||
|
'admin-password': 'openstack',
|
||||||
|
'admin-token': 'ubuntutesting',
|
||||||
|
}
|
||||||
|
manila_config = {
|
||||||
|
'default-share-backend': 'generic',
|
||||||
|
}
|
||||||
|
manila_generic_config = {
|
||||||
|
'driver-handles-share-servers': False,
|
||||||
|
}
|
||||||
|
configs = {
|
||||||
|
'keystone': keystone_config,
|
||||||
|
'manila': manila_config,
|
||||||
|
'manila-generic': manila_generic_config,
|
||||||
|
}
|
||||||
|
super(ManilaGenericBasicDeployment, self)._configure_services(configs)
|
||||||
|
|
||||||
|
def _initialize_tests(self):
|
||||||
|
"""Perform final initialization before tests get run."""
|
||||||
|
# Access the sentries for inspecting service units
|
||||||
|
self.manila_sentry = self.d.sentry['manila'][0]
|
||||||
|
self.manila_generic_sentry = self.d.sentry['manila-generic'][0]
|
||||||
|
self.mysql_sentry = self.d.sentry['mysql'][0]
|
||||||
|
self.keystone_sentry = self.d.sentry['keystone'][0]
|
||||||
|
self.rabbitmq_sentry = self.d.sentry['rabbitmq-server'][0]
|
||||||
|
u.log.debug('openstack release val: {}'.format(
|
||||||
|
self._get_openstack_release()))
|
||||||
|
u.log.debug('openstack release str: {}'.format(
|
||||||
|
self._get_openstack_release_string()))
|
||||||
|
|
||||||
|
keystone_ip = self.keystone_sentry.relation(
|
||||||
|
'shared-db', 'mysql:shared-db')['private-address']
|
||||||
|
|
||||||
|
# We need to auth either to v2.0 or v3 keystone
|
||||||
|
if self._keystone_version == '2':
|
||||||
|
ep = ("http://{}:35357/v2.0"
|
||||||
|
.format(keystone_ip.strip().decode('utf-8')))
|
||||||
|
auth = keystone_identity.v2.Password(
|
||||||
|
username='admin',
|
||||||
|
password='openstack',
|
||||||
|
tenant_name='admin',
|
||||||
|
auth_url=ep)
|
||||||
|
keystone_client_lib = keystone_v2_0_client
|
||||||
|
elif self._keystone_version == '3':
|
||||||
|
ep = ("http://{}:35357/v3"
|
||||||
|
.format(keystone_ip.strip().decode('utf-8')))
|
||||||
|
auth = keystone_identity.v3.Password(
|
||||||
|
user_domain_name='admin_domain',
|
||||||
|
username='admin',
|
||||||
|
password='openstack',
|
||||||
|
domain_name='admin_domain',
|
||||||
|
auth_url=ep)
|
||||||
|
keystone_client_lib = keystone_v3_client
|
||||||
|
else:
|
||||||
|
raise RuntimeError("keystone version must be '2' or '3'")
|
||||||
|
|
||||||
|
sess = keystone_session.Session(auth=auth)
|
||||||
|
self.keystone = keystone_client_lib.Client(session=sess)
|
||||||
|
# The service_catalog is missing from V3 keystone client when auth is
|
||||||
|
# done with session (via authenticate_keystone_admin()
|
||||||
|
# See https://bugs.launchpad.net/python-keystoneclient/+bug/1508374
|
||||||
|
# using session construct client will miss service_catalog property
|
||||||
|
# workaround bug # 1508374 by forcing a pre-auth and therefore, getting
|
||||||
|
# the service-catalog --
|
||||||
|
# see https://bugs.launchpad.net/python-keystoneclient/+bug/1547331
|
||||||
|
self.keystone.auth_ref = auth.get_access(sess)
|
||||||
|
|
||||||
|
def test_205_manila_to_manila_generic(self):
|
||||||
|
"""Verify that the manila to manila-generic config is working"""
|
||||||
|
u.log.debug('Checking the manila:manila-generic relation data...')
|
||||||
|
manila = self.manila_sentry
|
||||||
|
relation = ['manila-plugin', 'manila-generic:manila-plugin']
|
||||||
|
expected = {
|
||||||
|
'private-address': u.valid_ip,
|
||||||
|
'_authentication_data': u.not_null,
|
||||||
|
}
|
||||||
|
ret = u.validate_relation_data(manila, relation, expected)
|
||||||
|
if ret:
|
||||||
|
message = u.relation_error('manila manila_generic', ret)
|
||||||
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
u.log.debug('OK')
|
||||||
|
|
||||||
|
def test_206_manila_generic_to_manila(self):
|
||||||
|
"""Verify that the manila-generic to manila config is working"""
|
||||||
|
u.log.debug('Checking the manila-generic:manila relation data...')
|
||||||
|
manila_generic = self.manila_generic_sentry
|
||||||
|
relation = ['manila-plugin', 'manila:manila-plugin']
|
||||||
|
expected = {
|
||||||
|
'private-address': u.valid_ip,
|
||||||
|
'_configuration_data': u.not_null,
|
||||||
|
'_name': 'generic'
|
||||||
|
}
|
||||||
|
ret = u.validate_relation_data(manila_generic, relation, expected)
|
||||||
|
if ret:
|
||||||
|
message = u.relation_error('manila manila_generic', ret)
|
||||||
|
amulet.raise_status(amulet.FAIL, msg=message)
|
||||||
|
u.log.debug('OK')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_or_create(items, key, create):
|
||||||
|
"""Find or create the thing in the items
|
||||||
|
|
||||||
|
:param items: the items to search using the key
|
||||||
|
:param key: a function that key(item) -> boolean if found.
|
||||||
|
:param create: a function to call if the key() never was true.
|
||||||
|
:returns: the item that was either found or created.
|
||||||
|
"""
|
||||||
|
for i in items:
|
||||||
|
if key(i):
|
||||||
|
return i
|
||||||
|
return create()
|
||||||
|
|
||||||
|
def test_400_api_connection(self):
|
||||||
|
"""Simple api calls to check service is up and responding"""
|
||||||
|
u.log.debug('Checking api functionality...')
|
||||||
|
|
||||||
|
# This handles both keystone v2 and v3.
|
||||||
|
# For keystone v2 we need a user:
|
||||||
|
# - 'demo' user
|
||||||
|
# - has a project 'demo'
|
||||||
|
# - in the 'demo' project
|
||||||
|
# - with an 'admin' role
|
||||||
|
# For keystone v3 we need a user:
|
||||||
|
# - 'default' domain
|
||||||
|
# - 'demo' user
|
||||||
|
# - 'demo' project
|
||||||
|
# - 'admin' role -- to be able to delete.
|
||||||
|
|
||||||
|
# manila requires a user with creator or admin role on the project
|
||||||
|
# when creating a secret (which this test does). Therefore, we create
|
||||||
|
# a demo user, demo project, and then get a demo manila client and do
|
||||||
|
# the secret. ensure that the default domain is created.
|
||||||
|
|
||||||
|
if self._keystone_version == '2':
|
||||||
|
# find or create the 'demo' tenant (project)
|
||||||
|
tenant = self._find_or_create(
|
||||||
|
items=self.keystone.tenants.list(),
|
||||||
|
key=lambda t: t.name == 'demo',
|
||||||
|
create=lambda: self.keystone.tenants.create(
|
||||||
|
tenant_name="demo",
|
||||||
|
description="Demo for testing manila",
|
||||||
|
enabled=True))
|
||||||
|
# find or create the demo user
|
||||||
|
demo_user = self._find_or_create(
|
||||||
|
items=self.keystone.users.list(),
|
||||||
|
key=lambda u: u.name == 'demo',
|
||||||
|
create=lambda: self.keystone.users.create(
|
||||||
|
name='demo',
|
||||||
|
password='pass',
|
||||||
|
tenant_id=tenant.id))
|
||||||
|
# find the admin role
|
||||||
|
# already be created - if not, then this will fail later.
|
||||||
|
admin_role = self._find_or_create(
|
||||||
|
items=self.keystone.roles.list(),
|
||||||
|
key=lambda r: r.name.lower() == 'admin',
|
||||||
|
create=lambda: None)
|
||||||
|
# grant the role if it isn't already created.
|
||||||
|
# now grant the creator role to the demo user.
|
||||||
|
self._find_or_create(
|
||||||
|
items=self.keystone.roles.roles_for_user(
|
||||||
|
demo_user, tenant=tenant),
|
||||||
|
key=lambda r: r.name.lower() == admin_role.name.lower(),
|
||||||
|
create=lambda: self.keystone.roles.add_user_role(
|
||||||
|
demo_user, admin_role, tenant=tenant))
|
||||||
|
# now we can finally get the manila client and create the secret
|
||||||
|
keystone_ep = self.keystone.service_catalog.url_for(
|
||||||
|
service_type='identity', endpoint_type='publicURL')
|
||||||
|
auth = keystone_identity.v2.Password(
|
||||||
|
username=demo_user.name,
|
||||||
|
password='pass',
|
||||||
|
tenant_name=tenant.name,
|
||||||
|
auth_url=keystone_ep)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# find or create the 'default' domain
|
||||||
|
domain = self._find_or_create(
|
||||||
|
items=self.keystone.domains.list(),
|
||||||
|
key=lambda u: u.name == 'default',
|
||||||
|
create=lambda: self.keystone.domains.create(
|
||||||
|
"default",
|
||||||
|
description="domain for manila testing",
|
||||||
|
enabled=True))
|
||||||
|
# find or create the 'demo' user
|
||||||
|
demo_user = self._find_or_create(
|
||||||
|
items=self.keystone.users.list(domain=domain.id),
|
||||||
|
key=lambda u: u.name == 'demo',
|
||||||
|
create=lambda: self.keystone.users.create(
|
||||||
|
'demo',
|
||||||
|
domain=domain.id,
|
||||||
|
description="Demo user for manila tests",
|
||||||
|
enabled=True,
|
||||||
|
email="demo@example.com",
|
||||||
|
password="pass"))
|
||||||
|
# find or create the 'demo' project
|
||||||
|
demo_project = self._find_or_create(
|
||||||
|
items=self.keystone.projects.list(domain=domain.id),
|
||||||
|
key=lambda x: x.name == 'demo',
|
||||||
|
create=lambda: self.keystone.projects.create(
|
||||||
|
'demo',
|
||||||
|
domain=domain.id,
|
||||||
|
description='manila testing project',
|
||||||
|
enabled=True))
|
||||||
|
# create the role for the user - needs to be admin so that the
|
||||||
|
# secret can be deleted - note there is only one admin role, and it
|
||||||
|
# should already be created - if not, then this will fail later.
|
||||||
|
admin_role = self._find_or_create(
|
||||||
|
items=self.keystone.roles.list(),
|
||||||
|
key=lambda r: r.name.lower() == 'admin',
|
||||||
|
create=lambda: None)
|
||||||
|
# now grant the creator role to the demo user.
|
||||||
|
try:
|
||||||
|
self.keystone.roles.check(
|
||||||
|
role=admin_role,
|
||||||
|
user=demo_user,
|
||||||
|
project=demo_project)
|
||||||
|
except keystoneclient.exceptions.NotFound:
|
||||||
|
# create it if it isn't found
|
||||||
|
self.keystone.roles.grant(
|
||||||
|
role=admin_role,
|
||||||
|
user=demo_user,
|
||||||
|
project=demo_project)
|
||||||
|
# now we can finally get the manila client and create the secret
|
||||||
|
keystone_ep = self.keystone.service_catalog.url_for(
|
||||||
|
service_type='identity', endpoint_type='publicURL')
|
||||||
|
auth = keystone_identity.v3.Password(
|
||||||
|
user_domain_name=domain.name,
|
||||||
|
username=demo_user.name,
|
||||||
|
password='pass',
|
||||||
|
project_domain_name=domain.name,
|
||||||
|
project_name=demo_project.name,
|
||||||
|
auth_url=keystone_ep)
|
||||||
|
|
||||||
|
# Now we carry on with common v2 and v3 code
|
||||||
|
sess = keystone_session.Session(auth=auth)
|
||||||
|
# Authenticate admin with manila endpoint
|
||||||
|
manila_ep = self.keystone.service_catalog.url_for(
|
||||||
|
service_type='share', endpoint_type='publicURL')
|
||||||
|
manila = manila_client.Client(session=sess,
|
||||||
|
endpoint=manila_ep)
|
||||||
|
# now just try a list the shares
|
||||||
|
manila.shares.list()
|
||||||
|
u.log.debug('OK')
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""Amulet tests on a basic barbican deployment on xenial-mitaka for keystone v2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from basic_deployment import ManilaGenericBasicDeployment
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
deployment = ManilaGenericBasicDeployment(series='xenial', keystone_version='2')
|
||||||
|
deployment.run_tests()
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Bootstrap the model if necessary.
|
||||||
|
bootstrap: True
|
||||||
|
# Re-use bootstrap node instead of destroying/re-bootstrapping.
|
||||||
|
reset: True
|
||||||
|
# Use tox/requirements to drive the venv instead of bundletester's venv feature.
|
||||||
|
virtualenv: False
|
||||||
|
# Leave makefile empty, otherwise unit/lint tests will rerun ahead of amulet.
|
||||||
|
makefile: []
|
||||||
|
# Do not specify juju PPA sources. Juju is presumed to be pre-installed
|
||||||
|
# and configured in all test runner environments.
|
||||||
|
#sources:
|
||||||
|
# Do not specify or rely on system packages.
|
||||||
|
#packages:
|
||||||
|
# Do not specify python packages here. Use test-requirements.txt
|
||||||
|
# and tox instead. ie. The venv is constructed before bundletester
|
||||||
|
# is invoked.
|
||||||
|
#python-packages:
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Source charm: ./src/tox.ini
|
||||||
|
# This file is managed centrally by release-tools and should not be modified
|
||||||
|
# within individual charm repos.
|
||||||
|
[tox]
|
||||||
|
envlist = pep8
|
||||||
|
skipsdist = True
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
PYTHONHASHSEED=0
|
||||||
|
AMULET_SETUP_TIMEOUT=2700
|
||||||
|
whitelist_externals = juju
|
||||||
|
passenv = HOME TERM AMULET_*
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
install_command =
|
||||||
|
pip install --allow-unverified python-apt {opts} {packages}
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
basepython = python2.7
|
||||||
|
commands = charm-proof
|
||||||
|
|
||||||
|
[testenv:func27-noop]
|
||||||
|
# DRY RUN - For Debug
|
||||||
|
basepython = python2.7
|
||||||
|
commands =
|
||||||
|
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" -n --no-destroy
|
||||||
|
|
||||||
|
[testenv:func27]
|
||||||
|
# Run all gate tests which are +x (expected to always pass)
|
||||||
|
basepython = python2.7
|
||||||
|
commands =
|
||||||
|
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" --no-destroy
|
||||||
|
|
||||||
|
[testenv:func27-smoke]
|
||||||
|
# Run a specific test as an Amulet smoke test (expected to always pass)
|
||||||
|
basepython = python2.7
|
||||||
|
commands =
|
||||||
|
bundletester -vl DEBUG -r json -o func-results.json gate-basic-xenial-mitaka --no-destroy
|
||||||
|
|
||||||
|
[testenv:func27-dfs]
|
||||||
|
# Run all deploy-from-source tests which are +x (may not always pass!)
|
||||||
|
basepython = python2.7
|
||||||
|
commands =
|
||||||
|
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dfs-*" --no-destroy
|
||||||
|
|
||||||
|
[testenv:func27-dev]
|
||||||
|
# Run all development test targets which are +x (may not always pass!)
|
||||||
|
basepython = python2.7
|
||||||
|
commands =
|
||||||
|
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dev-*" --no-destroy
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Lint and unit test requirements
|
||||||
|
flake8
|
||||||
|
os-testr>=0.4.1
|
||||||
|
charms.reactive
|
||||||
|
mock>=1.2
|
||||||
|
coverage>=3.6
|
||||||
|
git+https://github.com/openstack/charms.openstack.git#egg=charms-openstack
|
|
@ -0,0 +1,53 @@
|
||||||
|
[tox]
|
||||||
|
skipsdist = True
|
||||||
|
envlist = pep8,py34,py35
|
||||||
|
skip_missing_interpreters = True
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
PYTHONHASHSEED=0
|
||||||
|
TERM=linux
|
||||||
|
INTERFACE_PATH={toxinidir}/interfaces
|
||||||
|
LAYER_PATH={toxinidir}/layers
|
||||||
|
INTERFACE_PATH={toxinidir}/interfaces
|
||||||
|
JUJU_REPOSITORY={toxinidir}/build
|
||||||
|
passenv = http_proxy https_proxy
|
||||||
|
install_command =
|
||||||
|
pip install {opts} {packages}
|
||||||
|
deps =
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
|
||||||
|
[testenv:build]
|
||||||
|
basepython = python2.7
|
||||||
|
commands =
|
||||||
|
charm-build --log-level DEBUG -o {toxinidir}/build src {posargs}
|
||||||
|
|
||||||
|
[testenv:py27]
|
||||||
|
basepython = python2.7
|
||||||
|
# Reactive source charms are Python3-only, but a py27 unit test target
|
||||||
|
# is required by OpenStack Governance. Remove this shim as soon as
|
||||||
|
# permitted. http://governance.openstack.org/reference/cti/python_cti.html
|
||||||
|
whitelist_externals = true
|
||||||
|
commands = true
|
||||||
|
|
||||||
|
[testenv:py34]
|
||||||
|
basepython = python3.4
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = ostestr {posargs}
|
||||||
|
|
||||||
|
[testenv:py35]
|
||||||
|
basepython = python3.5
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = ostestr {posargs}
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
basepython = python2.7
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = flake8 {posargs} src unit_tests
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
# E402 ignore necessary for path append before sys module import in actions
|
||||||
|
ignore = E402
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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 sys
|
||||||
|
import mock
|
||||||
|
|
||||||
|
sys.path.append('src')
|
||||||
|
sys.path.append('src/lib')
|
||||||
|
|
||||||
|
# Mock out charmhelpers so that we can test without it.
|
||||||
|
# also stops sideeffects from occuring.
|
||||||
|
charmhelpers = mock.MagicMock()
|
||||||
|
apt_pkg = mock.MagicMock()
|
||||||
|
sys.modules['apt_pkg'] = apt_pkg
|
||||||
|
sys.modules['charmhelpers'] = charmhelpers
|
||||||
|
sys.modules['charmhelpers.core'] = charmhelpers.core
|
||||||
|
sys.modules['charmhelpers.core.hookenv'] = charmhelpers.core.hookenv
|
||||||
|
sys.modules['charmhelpers.core.host'] = charmhelpers.core.host
|
||||||
|
sys.modules['charmhelpers.core.unitdata'] = charmhelpers.core.unitdata
|
||||||
|
sys.modules['charmhelpers.core.templating'] = charmhelpers.core.templating
|
||||||
|
sys.modules['charmhelpers.contrib'] = charmhelpers.contrib
|
||||||
|
sys.modules['charmhelpers.contrib.openstack'] = charmhelpers.contrib.openstack
|
||||||
|
sys.modules['charmhelpers.contrib.openstack.utils'] = (
|
||||||
|
charmhelpers.contrib.openstack.utils)
|
||||||
|
sys.modules['charmhelpers.contrib.openstack.templating'] = (
|
||||||
|
charmhelpers.contrib.openstack.templating)
|
||||||
|
sys.modules['charmhelpers.contrib.network'] = charmhelpers.contrib.network
|
||||||
|
sys.modules['charmhelpers.contrib.network.ip'] = (
|
||||||
|
charmhelpers.contrib.network.ip)
|
||||||
|
sys.modules['charmhelpers.fetch'] = charmhelpers.fetch
|
||||||
|
sys.modules['charmhelpers.cli'] = charmhelpers.cli
|
||||||
|
sys.modules['charmhelpers.contrib.hahelpers'] = charmhelpers.contrib.hahelpers
|
||||||
|
sys.modules['charmhelpers.contrib.hahelpers.cluster'] = (
|
||||||
|
charmhelpers.contrib.hahelpers.cluster)
|
|
@ -0,0 +1,382 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
import charm.openstack.manila_generic as manila_generic
|
||||||
|
|
||||||
|
import charms_openstack.test_utils as test_utils
|
||||||
|
|
||||||
|
|
||||||
|
class Helper(test_utils.PatchHelper):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.patch_release(manila_generic.ManilaGenericCharm.release)
|
||||||
|
|
||||||
|
|
||||||
|
class TestManilaGenericCharmConfigProperties(Helper):
|
||||||
|
|
||||||
|
def test_computed_use_password(self):
|
||||||
|
config = mock.MagicMock()
|
||||||
|
# test no passowrd or driver_auth_type configured
|
||||||
|
config.driver_service_instance_password = None
|
||||||
|
config.driver_auth_type = None
|
||||||
|
self.assertFalse(manila_generic.computed_use_password(config))
|
||||||
|
# test with the password but no auth type configured.
|
||||||
|
config.driver_service_instance_password = 'hello'
|
||||||
|
self.assertFalse(manila_generic.computed_use_password(config))
|
||||||
|
# test with a driver password, and a configured string, but not
|
||||||
|
# password or both.
|
||||||
|
config.driver_auth_type = 'goodbye'
|
||||||
|
self.assertFalse(manila_generic.computed_use_password(config))
|
||||||
|
# test with 'password'
|
||||||
|
config.driver_auth_type = 'Password'
|
||||||
|
self.assertTrue(manila_generic.computed_use_password(config))
|
||||||
|
# test with 'BOTH'
|
||||||
|
config.driver_auth_type = 'BOTH'
|
||||||
|
self.assertTrue(manila_generic.computed_use_password(config))
|
||||||
|
# now test without the password again.
|
||||||
|
config.driver_service_instance_password = None
|
||||||
|
self.assertFalse(manila_generic.computed_use_password(config))
|
||||||
|
|
||||||
|
def test_computed_use_ssh(self):
|
||||||
|
config = mock.MagicMock()
|
||||||
|
# test that not being configured returns false.
|
||||||
|
config.driver_auth_type = None
|
||||||
|
self.assertFalse(manila_generic.computed_use_ssh(config))
|
||||||
|
# check that being either ssh or 'both' in upper/lower gives true
|
||||||
|
config.driver_auth_type = 'Ssh'
|
||||||
|
self.assertTrue(manila_generic.computed_use_ssh(config))
|
||||||
|
config.driver_auth_type = 'BOTH'
|
||||||
|
self.assertTrue(manila_generic.computed_use_ssh(config))
|
||||||
|
config.driver_auth_type = 'both'
|
||||||
|
self.assertTrue(manila_generic.computed_use_ssh(config))
|
||||||
|
|
||||||
|
def test_computed_define_ssh(self):
|
||||||
|
config = mock.MagicMock()
|
||||||
|
config.driver_service_ssh_key = None
|
||||||
|
config.driver_service_ssh_key_public = None
|
||||||
|
# test that function only returns true if both config items are set
|
||||||
|
self.assertFalse(manila_generic.computed_define_ssh(config))
|
||||||
|
config.driver_service_ssh_key = "ssh key"
|
||||||
|
config.driver_service_ssh_key_public = None
|
||||||
|
self.assertFalse(manila_generic.computed_define_ssh(config))
|
||||||
|
config.driver_service_ssh_key = None
|
||||||
|
config.driver_service_ssh_key_public = "ssh public key"
|
||||||
|
self.assertFalse(manila_generic.computed_define_ssh(config))
|
||||||
|
config.driver_service_ssh_key = "ssh key"
|
||||||
|
config.driver_service_ssh_key_public = "ssh public key"
|
||||||
|
self.assertTrue(manila_generic.computed_define_ssh(config))
|
||||||
|
|
||||||
|
def test_computed_debug_level(self):
|
||||||
|
config = mock.MagicMock()
|
||||||
|
config.debug = False
|
||||||
|
config.verbose = False
|
||||||
|
self.assertEqual(manila_generic.computed_debug_level(config), "NONE")
|
||||||
|
config.verbose = True
|
||||||
|
self.assertEqual(manila_generic.computed_debug_level(config), "NONE")
|
||||||
|
config.debug = True
|
||||||
|
config.verbose = False
|
||||||
|
self.assertEqual(
|
||||||
|
manila_generic.computed_debug_level(config), "WARNING")
|
||||||
|
config.verbose = True
|
||||||
|
self.assertEqual(manila_generic.computed_debug_level(config), "DEBUG")
|
||||||
|
|
||||||
|
|
||||||
|
class TestManilaGenericCharm(Helper):
|
||||||
|
|
||||||
|
def _patch_config_and_charm(self, config):
|
||||||
|
self.patch('charmhelpers.core.hookenv.config', name='config')
|
||||||
|
|
||||||
|
def cf(key=None):
|
||||||
|
if key is not None:
|
||||||
|
return config[key]
|
||||||
|
return config
|
||||||
|
|
||||||
|
self.config.side_effect = cf
|
||||||
|
|
||||||
|
def test_custom_assess_status_check(self):
|
||||||
|
config = {
|
||||||
|
'driver-handles-share-servers': False,
|
||||||
|
'driver-service-image-name': '',
|
||||||
|
'driver-service-instance-user': '',
|
||||||
|
'driver-service-instance-flavor-id': '',
|
||||||
|
'driver-service-instance-password': '',
|
||||||
|
'driver-keypair-name': '',
|
||||||
|
}
|
||||||
|
self._patch_config_and_charm(config)
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(c.custom_assess_status_check(), (None, None))
|
||||||
|
config['driver-handles-share-servers'] = True
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(c.custom_assess_status_check(),
|
||||||
|
('blocked', "Missing 'driver-service-image-name'"))
|
||||||
|
config['driver-service-image-name'] = 'image-name'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(c.custom_assess_status_check(),
|
||||||
|
('blocked', "Missing 'driver-service-instance-user'"))
|
||||||
|
config['driver-service-instance-user'] = 'manila'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(
|
||||||
|
c.custom_assess_status_check(),
|
||||||
|
('blocked', "Missing 'driver-service-instance-flavor-id'"))
|
||||||
|
config['driver-service-instance-flavor-id'] = '100'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(
|
||||||
|
c.custom_assess_status_check(),
|
||||||
|
('blocked',
|
||||||
|
"Need at least one of instance password or keypair name"))
|
||||||
|
config['driver-service-instance-password'] = 'password'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(c.custom_assess_status_check(), (None, None))
|
||||||
|
config['driver-service-instance-password'] = ''
|
||||||
|
config['driver-keypair-name'] = 'keyname'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(c.custom_assess_status_check(), (None, None))
|
||||||
|
config['driver-service-instance-password'] = 'password'
|
||||||
|
config['driver-keypair-name'] = 'keyname'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(c.custom_assess_status_check(), (None, None))
|
||||||
|
|
||||||
|
def test_get_config_for_principal(self):
|
||||||
|
# note that this indirectly tests 'process_lines' as well.
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(
|
||||||
|
c.get_config_for_principal(None),
|
||||||
|
{'complete': False, 'reason': 'No authentication data'})
|
||||||
|
# we want to handle share servers to True to check for misconfig
|
||||||
|
config = {
|
||||||
|
'driver-handles-share-servers': True,
|
||||||
|
'driver-service-image-name': '',
|
||||||
|
'driver-service-instance-user': '',
|
||||||
|
'driver-service-instance-flavor-id': '',
|
||||||
|
'driver-service-instance-password': '',
|
||||||
|
'driver-keypair-name': '',
|
||||||
|
'share-backend-name': '',
|
||||||
|
'driver-auth-type': '',
|
||||||
|
'driver-connect-share-server-to-tenant-network': False,
|
||||||
|
}
|
||||||
|
self._patch_config_and_charm(config)
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
state, message = c.custom_assess_status_check()
|
||||||
|
auth_data = {
|
||||||
|
'username': 'user',
|
||||||
|
'password': 'pass',
|
||||||
|
'project_domain_id': 'pd1',
|
||||||
|
'project_name': 'p1',
|
||||||
|
'user_domain_id': 'ud1',
|
||||||
|
'auth_uri': 'uri1',
|
||||||
|
'auth_url': 'url1',
|
||||||
|
'auth_type': 'type1',
|
||||||
|
}
|
||||||
|
self.maxDiff = None
|
||||||
|
self.assertEqual(
|
||||||
|
c.get_config_for_principal(auth_data),
|
||||||
|
{'complete': False, 'reason': message})
|
||||||
|
# now set up the config to be okay to generate the sections
|
||||||
|
config['driver-handles-share-servers'] = True
|
||||||
|
config['driver-service-image-name'] = 'manila'
|
||||||
|
config['driver-service-instance-user'] = 'manila-user'
|
||||||
|
config['driver-service-instance-flavor-id'] = '103'
|
||||||
|
config['driver-service-instance-password'] = 'password'
|
||||||
|
config['driver-keypair-name'] = 'my-keyname'
|
||||||
|
# test that we've set the backend name
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.assertEqual(
|
||||||
|
c.get_config_for_principal(auth_data),
|
||||||
|
{'complete': False, 'reason':
|
||||||
|
'Problem: share-backend-name is not set'})
|
||||||
|
# now test that we actually generate some config data
|
||||||
|
config['share-backend-name'] = 'test-backend'
|
||||||
|
# simplify the output for the next test
|
||||||
|
config['driver-handles-share-servers'] = False
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
lines = c.get_config_for_principal(auth_data)
|
||||||
|
# verify that "# No generic password section" is in the lines
|
||||||
|
conf = manila_generic.MANILA_CONF
|
||||||
|
self.assertIn(conf, lines)
|
||||||
|
self.assertIn('[test-backend]', lines[conf])
|
||||||
|
section = lines[conf]['[test-backend]']
|
||||||
|
self.assertIn('share_driver = '
|
||||||
|
'manila.share.drivers.generic.GenericShareDriver',
|
||||||
|
section)
|
||||||
|
self.assertIn('driver_handles_share_servers = False', section)
|
||||||
|
self.assertIn('share_backend_name = test-backend', section)
|
||||||
|
|
||||||
|
# Now verify that when we switch the driver handles shares on that the
|
||||||
|
# sections all appear
|
||||||
|
config['driver-handles-share-servers'] = True
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
lines = c.get_config_for_principal(auth_data)
|
||||||
|
self.assertIn(conf, lines)
|
||||||
|
self.assertIn('[test-backend]', lines[conf])
|
||||||
|
self.assertIn('[nova]', lines[conf])
|
||||||
|
self.assertIn('[neutron]', lines[conf])
|
||||||
|
self.assertIn('[cinder]', lines[conf])
|
||||||
|
# check each of the nova, neutron and cinder sections (which are all
|
||||||
|
# identical)
|
||||||
|
auth_lines = ['# Only needed for the generic drivers as of Mitaka',
|
||||||
|
'username = user',
|
||||||
|
'password = pass',
|
||||||
|
'project_domain_id = pd1',
|
||||||
|
'project_name = p1',
|
||||||
|
'user_domain_id = ud1',
|
||||||
|
'auth_uri = uri1',
|
||||||
|
'auth_url = url1',
|
||||||
|
'auth_type = type1']
|
||||||
|
|
||||||
|
for s in ('[nova]', '[neutron]', '[cinder]'):
|
||||||
|
section = lines[conf][s]
|
||||||
|
self._verify_section_contains(section, auth_lines)
|
||||||
|
|
||||||
|
# now check the [test-backend] section
|
||||||
|
section = lines[conf]['[test-backend]']
|
||||||
|
self.assertIn('share_driver = '
|
||||||
|
'manila.share.drivers.generic.GenericShareDriver',
|
||||||
|
section)
|
||||||
|
self.assertIn('driver_handles_share_servers = True', section)
|
||||||
|
self.assertIn('share_backend_name = test-backend', section)
|
||||||
|
self.assertIn('service_instance_flavor_id = 103', section)
|
||||||
|
self._verify_section_contains(
|
||||||
|
section,
|
||||||
|
['service_instance_user = manila-user',
|
||||||
|
'service_image_name = manila',
|
||||||
|
'connect_share_server_to_tenant_network = False'])
|
||||||
|
self._verify_section_contains(
|
||||||
|
section,
|
||||||
|
['# No generic password section',
|
||||||
|
'# No ssh section', ])
|
||||||
|
|
||||||
|
# Now switch on the password section
|
||||||
|
config['driver-auth-type'] = 'password'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
lines = c.get_config_for_principal(auth_data)
|
||||||
|
section = lines[conf]['[test-backend]']
|
||||||
|
self.assertNotIn('# No generic password section', section)
|
||||||
|
self.assertIn('service_instance_password = password', section)
|
||||||
|
|
||||||
|
# Now switch on the SSH section
|
||||||
|
config['driver_service_ssh_key'] = 'ssh-key'
|
||||||
|
config['driver-service-ssh-key-public'] = 'ssh-key-public'
|
||||||
|
config['driver-auth-type'] = 'ssh'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
lines = c.get_config_for_principal(auth_data)
|
||||||
|
section = lines[conf]['[test-backend]']
|
||||||
|
self.assertNotIn('# No ssh section', section)
|
||||||
|
self.assertIn('# No generic password section', section)
|
||||||
|
# test for ssh lines
|
||||||
|
self._verify_section_contains(
|
||||||
|
section,
|
||||||
|
['path_to_private_key = {}'
|
||||||
|
.format(manila_generic.MANILA_SSH_KEY_PATH),
|
||||||
|
'path_to_public_key = {}'
|
||||||
|
.format(manila_generic.MANILA_SSH_KEY_PATH_PUBLIC),
|
||||||
|
'manila_service_keypair_name = my-keyname', ])
|
||||||
|
|
||||||
|
# Enable the connect_share_to_tenant_network and both password and ssh
|
||||||
|
config['driver-auth-type'] = 'both'
|
||||||
|
config['driver-connect-share-server-to-tenant-network'] = True
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
lines = c.get_config_for_principal(auth_data)
|
||||||
|
section = lines[conf]['[test-backend]']
|
||||||
|
self.assertNotIn('# No ssh section', section)
|
||||||
|
self.assertNotIn('# No generic password section', section)
|
||||||
|
self.assertIn('service_instance_password = password', section)
|
||||||
|
# test for ssh lines
|
||||||
|
self._verify_section_contains(
|
||||||
|
section,
|
||||||
|
['path_to_private_key = {}'
|
||||||
|
.format(manila_generic.MANILA_SSH_KEY_PATH),
|
||||||
|
'path_to_public_key = {}'
|
||||||
|
.format(manila_generic.MANILA_SSH_KEY_PATH_PUBLIC),
|
||||||
|
'manila_service_keypair_name = my-keyname', ])
|
||||||
|
self.assertIn('connect_share_server_to_tenant_network = True', section)
|
||||||
|
|
||||||
|
def _verify_section_contains(self, section, lines):
|
||||||
|
index = section.index(lines[0])
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
self.assertEqual(section[index + i], line)
|
||||||
|
|
||||||
|
def test_maybe_write_ssh_keys(self):
|
||||||
|
config = {
|
||||||
|
'driver-keypair-name': '',
|
||||||
|
'driver-auth-type': '',
|
||||||
|
'driver-service-ssh-key': '',
|
||||||
|
'driver-service-ssh-key-public': ''
|
||||||
|
}
|
||||||
|
self._patch_config_and_charm(config)
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
# The 'maybe_write_ssh_keys' should attempt to delete two files
|
||||||
|
self.patch_object(manila_generic.os, 'remove')
|
||||||
|
c.maybe_write_ssh_keys()
|
||||||
|
self.assertEqual(self.remove.call_count, 2)
|
||||||
|
print(self.remove.call_args_list)
|
||||||
|
self.assertEqual(self.remove.call_args_list, [
|
||||||
|
mock.call(manila_generic.MANILA_SSH_KEY_PATH),
|
||||||
|
mock.call(manila_generic.MANILA_SSH_KEY_PATH_PUBLIC)])
|
||||||
|
# now configure it up and check the writes happen
|
||||||
|
config['driver-keypair-name'] = 'mykeypair'
|
||||||
|
config['driver-auth-type'] = 'both'
|
||||||
|
config['driver-service-ssh-key'] = 'this is my key'
|
||||||
|
config['driver-service-ssh-key-public'] = 'my public key'
|
||||||
|
c = manila_generic.ManilaGenericCharm()
|
||||||
|
self.patch_object(manila_generic, 'write_file')
|
||||||
|
c.maybe_write_ssh_keys()
|
||||||
|
self.assertEqual(self.write_file.call_count, 2)
|
||||||
|
self.write_file.assert_has_calls(
|
||||||
|
[mock.call('this is my key', manila_generic.MANILA_SSH_KEY_PATH),
|
||||||
|
mock.call('my public key',
|
||||||
|
manila_generic.MANILA_SSH_KEY_PATH_PUBLIC,
|
||||||
|
0o644)])
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuxilaryFunctions(Helper):
|
||||||
|
|
||||||
|
def test_write_file(self):
|
||||||
|
f = mock.MagicMock()
|
||||||
|
self.patch_object(manila_generic.os, 'fdopen', return_value=f)
|
||||||
|
self.patch_object(manila_generic.os, 'open', return_value='opener')
|
||||||
|
text = """
|
||||||
|
This
|
||||||
|
One"""
|
||||||
|
# strip the first new line off when passing the test string through
|
||||||
|
# this is to test dedenting strings
|
||||||
|
manila_generic.write_file(text[1:], 'file1')
|
||||||
|
self.open.assert_called_once_with(
|
||||||
|
'file1',
|
||||||
|
manila_generic.os.O_WRONLY | manila_generic.os.O_CREAT,
|
||||||
|
0o600)
|
||||||
|
self.fdopen.assert_called_once_with('opener', 'w')
|
||||||
|
f.__enter__().write.assert_called_once_with("This\nOne")
|
||||||
|
|
||||||
|
def test_write_file_private(self):
|
||||||
|
f = mock.MagicMock()
|
||||||
|
self.patch_object(manila_generic.os, 'fdopen', return_value=f)
|
||||||
|
self.patch_object(manila_generic.os, 'open', return_value='opener')
|
||||||
|
text = """
|
||||||
|
This
|
||||||
|
Two"""
|
||||||
|
# strip the first new line off when passing the test string through
|
||||||
|
# this is to test dedenting strings
|
||||||
|
manila_generic.write_file(text[1:], 'file1', chown=0o644)
|
||||||
|
self.open.assert_called_once_with(
|
||||||
|
'file1',
|
||||||
|
manila_generic.os.O_WRONLY | manila_generic.os.O_CREAT,
|
||||||
|
0o644)
|
||||||
|
self.fdopen.assert_called_once_with('opener', 'w')
|
||||||
|
f.__enter__().write.assert_called_once_with("This\nTwo")
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Copyright 2016 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
import reactive.manila_generic_handlers as handlers
|
||||||
|
|
||||||
|
import charms_openstack.test_utils as test_utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestRegisteredHooks(test_utils.TestRegisteredHooks):
|
||||||
|
|
||||||
|
def test_hooks(self):
|
||||||
|
defaults = [
|
||||||
|
'charm.installed',
|
||||||
|
'update-status']
|
||||||
|
hook_set = {
|
||||||
|
'when': {
|
||||||
|
'send_config': ('manila-plugin.available', ),
|
||||||
|
'update_config': ('manila-plugin.available',
|
||||||
|
'config.changed', ),
|
||||||
|
},
|
||||||
|
'when_not': {
|
||||||
|
'send_config': ('config.changed', ),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
# test that the hooks were registered via the
|
||||||
|
# reactive.barbican_handlers
|
||||||
|
self.registered_hooks_test_helper(handlers, hook_set, defaults)
|
||||||
|
|
||||||
|
|
||||||
|
class TestHandlerFunctions(test_utils.PatchHelper):
|
||||||
|
|
||||||
|
def _patch_provide_charm_instance(self):
|
||||||
|
manila_generic_charm = mock.MagicMock()
|
||||||
|
self.patch('charms_openstack.charm.provide_charm_instance',
|
||||||
|
name='provide_charm_instance',
|
||||||
|
new=mock.MagicMock())
|
||||||
|
self.provide_charm_instance().__enter__.return_value = \
|
||||||
|
manila_generic_charm
|
||||||
|
self.provide_charm_instance().__exit__.return_value = None
|
||||||
|
return manila_generic_charm
|
||||||
|
|
||||||
|
def test_send_config(self):
|
||||||
|
generic = self._patch_provide_charm_instance()
|
||||||
|
|
||||||
|
class FakeManilaPlugin(object):
|
||||||
|
|
||||||
|
name = None
|
||||||
|
configuration_data = None
|
||||||
|
authentication_data = 'auth data'
|
||||||
|
|
||||||
|
generic.get_config_for_principal.return_value = "some data"
|
||||||
|
manila_plugin = FakeManilaPlugin()
|
||||||
|
handlers.send_config(manila_plugin)
|
||||||
|
|
||||||
|
# test for expecations
|
||||||
|
self.assertEqual(manila_plugin.name,
|
||||||
|
generic.options.share_backend_name)
|
||||||
|
self.assertEqual(manila_plugin.configuration_data, "some data")
|
||||||
|
generic.get_config_for_principal.assert_called_once_with('auth data')
|
||||||
|
generic.assess_status.assert_called_once_with()
|
||||||
|
generic.maybe_write_ssh_keys.assert_called_once_with()
|
Loading…
Reference in New Issue