Use a single keystone instance for all the clients

This commit allows flame to be used with either a login and a password
or an OS_AUTH_TOKEN.

Change-Id: Ie34f2a4e2e0eb82967c7617bd0ecc1aebd934cb7
This commit is contained in:
Aurélien GOULON 2016-05-04 12:11:54 +02:00 committed by Guillaume Espanel
parent c9ee415228
commit 168d10bb1c
7 changed files with 145 additions and 94 deletions

View File

@ -13,8 +13,9 @@ for Nova (key pairs and servers), Cinder (volumes) and Neutron (router,
networks, subnets, security groups and floating IPs) resources.
`flame` works as follows: using provided credentials (user name, project name,
password, authentication url), the tool will list supported resources deployed
in the project and will generate corresponding, highly customized HOT template.
password or auth_token, authentication url), the tool will list supported
resources deployed in the project and will generate corresponding, highly
customized HOT template.
Installation
------------
@ -46,6 +47,8 @@ Usage
--project PROJECT Name of project. Defaults to env[OS_TENANT_NAME]
--region REGION Name of region. Defaults to env[OS_REGION_NAME]
--auth_url AUTH_URL Authentication URL. Defaults to env[OS_AUTH_URL].
--os-auth-token OS_AUTH_TOKEN
User's auth token. Defaults to env[OS_AUTH_TOKEN].
--insecure Explicitly allow clients to perform"insecure" SSL
(https) requests. The server's certificate will not be
verified against any certificate authorities. This
@ -67,3 +70,7 @@ To use Flame you can provide yours OpenStack credentials as arguments :
Or you can source your OpenStack RC file and use Flame without arguments.
Flame can be used with either a login and password pair or a keystone
token by exporting the OS_AUTH_TOKEN variable (the token is obtained
with keystone token-get).

View File

@ -9,8 +9,9 @@ To use install flame in a project::
To use the CLI of flame::
usage: flame [-h] [--username USERNAME] [--password PASSWORD]
[--project PROJECT] [--auth_url AUTH_URL] [--insecure]
[--exclude_servers] [--exclude_volumes]
[--project PROJECT] [--os-auth-token OS_AUTH_TOKEN]
[--auth_url AUTH_URL] [--insecure] [--exclude_servers]
[--exclude_volumes]
Generate Heat Template
@ -21,6 +22,8 @@ To use the CLI of flame::
--password PASSWORD The user's password. Defaults to env[OS_PASSWORD]
--project PROJECT Name of project. Defaults to env[OS_TENANT_NAME]
--auth_url AUTH_URL Authentication URL. Defaults to env[OS_AUTH_URL].
--os-auth-token OS_AUTH_TOKEN
User's auth token. Defaults to env[OS_AUTH_TOKEN].
--insecure Explicitly allow clients to perform"insecure" SSL
(https) requests. The server's certificate will not be
verified against any certificate authorities. This
@ -30,7 +33,7 @@ To use the CLI of flame::
--generate-stack-data
In addition to template, generate Heat stack data
file.
Example
-------
@ -40,6 +43,11 @@ To use Flame you can provide yours OpenStack credentials as arguments::
$ flame --username arezmerita --password password \
--project project-arezmerita --auth_url https://example.com/v2.0/
Or a token and a tenant::
$ flame --username arezmerita --os-auth-token keystonetoken \
--project project-arezmerita --auth_url https://example.com/v2.0/
Or you can source your OpenStack RC file and use Flame without arguments::
$ source credential.rc

View File

@ -26,12 +26,24 @@ from flameclient.flame import TemplateGenerator # noqa
class Client(object):
def __init__(self, username, password, tenant_name, auth_url, **kwargs):
def __init__(self, username, password, tenant_name, auth_url, auth_token,
**kwargs):
self.template_generator = TemplateGenerator(username, password,
tenant_name, auth_url,
auth_token,
**kwargs)
def generate(self, include_networks, include_instances, include_volumes):
return self.template_generator.generate(include_networks,
include_instances,
include_volumes)
def generate(self, exclude_servers, exclude_volumes, exclude_keypairs,
generate_stack_data):
self.template_generator.extract_vm_details(exclude_servers,
exclude_volumes,
exclude_keypairs,
generate_stack_data
)
self.template_generator.extract_data()
heat_template = self.template_generator.heat_template()
if generate_stack_data:
stack_data = self.template_generator.stack_data_template()
else:
stack_data = None
return (heat_template, stack_data)

View File

@ -53,6 +53,10 @@ def main(args=None):
default=os.environ.get("OS_AUTH_URL"),
help="Authentication URL. "
"Defaults to env[OS_AUTH_URL].")
parser.add_argument("--os-auth-token", type=str,
default=os.environ.get("OS_AUTH_TOKEN"),
help="User's auth token. "
"Defaults to env[OS_AUTH_TOKEN].")
parser.add_argument('--insecure', action='store_true', default=False,
help="Explicitly allow clients to perform"
"\"insecure\" SSL (https) requests. The "
@ -80,9 +84,10 @@ def main(args=None):
args = parser.parse_args()
flame = client.Client(args.username, args.password,
args.project, args.auth_url,
insecure=args.insecure,
args.os_auth_token,
region_name=args.region,
endpoint_type=args.endpoint_type,
region_name=args.region)
insecure=args.insecure)
template = flame.template_generator
template.extract_vm_details(args.exclude_servers,
args.exclude_volumes,

View File

@ -105,11 +105,12 @@ class Resource(object):
class TemplateGenerator(object):
def __init__(self, username, password, tenant_name, auth_url,
insecure=False, endpoint_type='publicURL', region_name=None):
auth_token=None, insecure=False, endpoint_type='publicURL',
region_name=None):
self.generate_data = False
self._setup_templates()
self._setup_managers(username, password, tenant_name, auth_url,
insecure, endpoint_type, region_name=region_name)
insecure, endpoint_type, region_name, auth_token)
def _setup_templates(self):
self.template = yaml.load(template_skeleton)
@ -120,19 +121,20 @@ class TemplateGenerator(object):
self.stack_data['resources'] = {}
def _setup_managers(self, username, password, tenant_name, auth_url,
insecure, endpoint_type, region_name=None):
self.neutron = managers.NeutronManager(username, password, tenant_name,
auth_url, insecure,
endpoint_type,
region_name=region_name)
self.nova = managers.NovaManager(username, password, tenant_name,
auth_url, insecure,
endpoint_type,
region_name=region_name)
self.cinder = managers.CinderManager(username, password, tenant_name,
auth_url, insecure,
endpoint_type,
region_name=region_name)
insecure, endpoint_type, region_name=None,
auth_token=None):
self.keystone = managers.KeystoneManager(
username, password,
tenant_name,
auth_url, insecure,
endpoint_type,
region_name=region_name,
auth_token=auth_token
)
self.keystone.authenticate()
self.neutron = managers.NeutronManager(self.keystone)
self.nova = managers.NovaManager(self.keystone)
self.cinder = managers.CinderManager(self.keystone)
def extract_vm_details(self, exclude_servers, exclude_volumes,
exclude_keypairs, generate_data):
@ -523,10 +525,13 @@ class TemplateGenerator(object):
"Snapshot to create volume %s from" % resource_name)
resource.add_parameter(key, description,
default=volume.snapshot_id)
if volume.display_name:
properties['name'] = volume.display_name
if volume.display_description:
properties['description'] = volume.display_description
try:
if volume.display_name:
properties['name'] = volume.display_name
if volume.display_description:
properties['description'] = volume.display_description
except AttributeError:
pass
if volume.volume_type and volume.volume_type != 'None':
key = "%s_volume_type" % resource_name
description = (

View File

@ -23,7 +23,6 @@
# SOFTWARE.
from cinderclient.v1 import client as cinder_client
from keystoneclient import exceptions as keystone_exceptions
from keystoneclient.v2_0 import client as keystone_client
from neutronclient.v2_0 import client as neutron_client
from novaclient import client as nova_client
@ -34,7 +33,7 @@ class KeystoneManager(object):
_client = None
def __init__(self, username, password, project, auth_url, insecure,
endpoint_type='publicURL', region_name=None):
endpoint_type='publicURL', region_name=None, auth_token=None):
self.username = username
self.password = password
self.project = project
@ -42,6 +41,11 @@ class KeystoneManager(object):
self.insecure = insecure
self.region_name = region_name
self.endpoint_type = endpoint_type
self.auth_token = auth_token
def authenticate(self):
self.client().authenticate()
self.auth_token = self.client().auth_token
def client(self):
if not self._client:
@ -52,7 +56,8 @@ class KeystoneManager(object):
auth_url=self.auth_url,
region_name=self.region_name,
insecure=self.insecure,
endpoint_type=self.endpoint_type)
endpoint_type=self.endpoint_type,
token=self.auth_token)
return self._client
def set_client(self, client):
@ -73,32 +78,19 @@ class NeutronManager(object):
_client = None
_project_id = None
def __init__(self, username, password, project, auth_url, insecure,
endpoint_type='publicURL', region_name=None):
self.username = username
self.password = password
self.project = project
self.auth_url = auth_url
self.insecure = insecure
self.endpoint_type = endpoint_type
self.region_name = region_name
def __init__(self, keystone_mgr):
self.keystone_mgr = keystone_mgr
def client(self):
if not self._client:
# Create the client
self._client = neutron_client.Client(
username=self.username,
password=self.password,
tenant_name=self.project,
auth_url=self.auth_url,
region_name=self.region_name,
insecure=self.insecure,
endpoint_type=self.endpoint_type)
auth_url=self.keystone_mgr.auth_url,
insecure=self.keystone_mgr.insecure,
endpoint_url=self.keystone_mgr.get_endpoint('network'),
token=self.keystone_mgr.auth_token)
if not self._project_id:
keystone_mgr = KeystoneManager(self.username, self.password,
self.project, self.auth_url,
self.insecure,
region_name=self.region_name)
self._project_id = keystone_mgr.get_project_id()
self._project_id = self.keystone_mgr.get_project_id()
return self._client
def set_client(self, client):
@ -142,24 +134,22 @@ class NovaManager(object):
"""Manage nova resources."""
_client = None
def __init__(self, username, password, project, auth_url, insecure,
endpoint_type='publicURL', region_name=None):
self.username = username
self.password = password
self.project = project
self.auth_url = auth_url
self.region_name = region_name
self.insecure = insecure
self.endpoint_type = endpoint_type
def __init__(self, keystone_mgr):
self.keystone_mgr = keystone_mgr
def client(self):
if not self._client:
self._client = nova_client.Client('2',
self.username, self.password,
self.project, self.auth_url,
region_name=self.region_name,
insecure=self.insecure,
endpoint_type=self.endpoint_type)
self._client = nova_client.Client(
'2',
self.keystone_mgr.username,
self.keystone_mgr.auth_token,
self.keystone_mgr.project,
self.keystone_mgr.auth_url,
region_name=self.keystone_mgr.region_name,
insecure=self.keystone_mgr.insecure,
endpoint_type=self.keystone_mgr.endpoint_type,
auth_token=self.keystone_mgr.auth_token
)
return self._client
def set_client(self, client):
@ -191,35 +181,27 @@ class CinderManager(object):
"""Manage Cinder resources."""
_client = None
def __init__(self, username, password, project, auth_url, insecure,
endpoint_type='publicURL', region_name=None):
self.username = username
self.password = password
self.project = project
self.auth_url = auth_url
self.region_name = region_name
self.insecure = insecure
def __init__(self, keystone_mgr):
self.keystone_mgr = keystone_mgr
self.defined = True
self.endpoint_type = endpoint_type
def client(self):
if self.defined and not self._client:
client = cinder_client.Client(self.username,
self.password,
self.project,
self.auth_url,
region_name=self.region_name,
insecure=self.insecure,
endpoint_type=self.endpoint_type)
# Check cinder endpoint existence
try:
client.authenticate()
self._client = client
except keystone_exceptions.EndpointNotFound:
self.defined = False
self._client = None
cinder_url = self.keystone_mgr.get_endpoint("volumev2")
except KeyError:
cinder_url = self.keystone_mgr.get_endpoint("volume")
client = cinder_client.Client(
self.keystone_mgr.username,
self.keystone_mgr.auth_token,
project_id=self.keystone_mgr.project,
auth_url=cinder_url,
http_log_debug=True,
insecure=self.keystone_mgr.insecure
)
client.client.auth_token = self.keystone_mgr.auth_token
client.client.management_url = cinder_url
self._client = client
return self._client
def set_client(self, client):

View File

@ -24,6 +24,7 @@
import mock
from flameclient import client as flame_client
from flameclient import flame
from flameclient.tests import base
@ -238,11 +239,16 @@ class BaseTestCase(base.TestCase):
self.mock_nova = self.patch_nova.start()
self.patch_cinder = mock.patch('flameclient.managers.CinderManager')
self.mock_cinder = self.patch_cinder.start()
self.patch_keystone = mock.patch(
'flameclient.managers.KeystoneManager'
)
self.mock_keystone = self.patch_keystone.start()
def tearDown(self):
self.mock_neutron.stop()
self.mock_nova.stop()
self.mock_cinder.stop()
self.mock_keystone.stop()
super(BaseTestCase, self).tearDown()
def get_generator(self, exclude_servers, exclude_volumes,
@ -274,6 +280,32 @@ class BaseTestCase(base.TestCase):
self.assertEqual(expected_parameters, merged_parameters)
class ClientTest(BaseTestCase):
def setUp(self):
super(ClientTest, self).setUp()
self.c = flame_client.Client('username', 'password', 'tenant_name',
'authUrl', 'auth_token')
def test_generate(self):
out = self.c.generate(False, False, False, True)
self.assertIsInstance(out, tuple)
self.assertIsNotNone(out[1])
def test_generate_no_stack_data(self):
out = self.c.generate(False, False, False, False)
self.assertIsInstance(out, tuple)
self.assertIsNotNone(out[0])
self.assertIsNone(out[1])
def test_generate_contains_extract(self):
generator = self.get_generator(False, False, False, True)
out = self.c.generate(False, False, False, True)
generator.extract_data()
stack_data = generator.stack_data_template()
heat_template = generator.heat_template()
self.assertEqual(out, (heat_template, stack_data))
class StackDataTests(BaseTestCase):
def test_keypair(self):