308 lines
12 KiB
Python
308 lines
12 KiB
Python
# Copyright 2013: Mirantis Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
""" Rally command: deployment """
|
|
|
|
from __future__ import print_function
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import jsonschema
|
|
from keystoneclient import exceptions as keystone_exceptions
|
|
from six.moves.urllib import parse
|
|
import yaml
|
|
|
|
from rally import api
|
|
from rally.cli import cliutils
|
|
from rally.cli import envutils
|
|
from rally.common import fileutils
|
|
from rally.common.i18n import _
|
|
from rally.common import utils
|
|
from rally import exceptions
|
|
from rally import plugins
|
|
|
|
|
|
class DeploymentCommands(object):
|
|
"""Set of commands that allow you to manage deployments."""
|
|
|
|
@cliutils.args("--name", type=str, required=True,
|
|
help="Name of the deployment.")
|
|
@cliutils.args("--fromenv", action="store_true",
|
|
help="Read environment variables instead of config file.")
|
|
@cliutils.args("--filename", type=str, required=False, metavar="<path>",
|
|
help="Path to the configuration file of the deployment.")
|
|
@cliutils.args("--no-use", action="store_false", dest="do_use",
|
|
help="Don't set new deployment as default for"
|
|
" future operations.")
|
|
@plugins.ensure_plugins_are_loaded
|
|
def create(self, name, fromenv=False, filename=None, do_use=False):
|
|
"""Create new deployment.
|
|
|
|
This command will create a new deployment record in rally
|
|
database. In the case of ExistingCloud deployment engine it
|
|
will use the cloud represented in the configuration. If the
|
|
cloud doesn't exist, Rally can deploy a new one for you with
|
|
Devstack or Fuel. Different deployment engines exist for these
|
|
cases.
|
|
|
|
If you use the ExistingCloud deployment engine you can pass
|
|
a deployment config by environment variables with ``--fromenv``:
|
|
|
|
OS_USERNAME
|
|
OS_PASSWORD
|
|
OS_AUTH_URL
|
|
OS_TENANT_NAME
|
|
OS_ENDPOINT
|
|
OS_REGION_NAME
|
|
OS_CACERT
|
|
OS_INSECURE
|
|
|
|
All other deployment engines need more complex configuration
|
|
data, so it should be stored in a configuration file.
|
|
|
|
You can use physical servers, LXC containers, KVM virtual
|
|
machines or virtual machines in OpenStack for deploying the
|
|
cloud. Except physical servers, Rally can create cluster nodes
|
|
for you. Interaction with virtualization software, OpenStack
|
|
cloud or physical servers is provided by server providers.
|
|
|
|
:param fromenv: boolean, read environment instead of config file
|
|
:param filename: path to the configuration file
|
|
:param name: name of the deployment
|
|
"""
|
|
|
|
if fromenv:
|
|
config = {"type": "ExistingCloud"}
|
|
config.update(envutils.get_creds_from_env_vars())
|
|
else:
|
|
if not filename:
|
|
print("Either --filename or --fromenv is required.")
|
|
return(1)
|
|
filename = os.path.expanduser(filename)
|
|
with open(filename, "rb") as deploy_file:
|
|
config = yaml.safe_load(deploy_file.read())
|
|
|
|
try:
|
|
deployment = api.Deployment.create(config, name)
|
|
except jsonschema.ValidationError:
|
|
print(_("Config schema validation error: %s.") % sys.exc_info()[1])
|
|
return(1)
|
|
except exceptions.DeploymentNameExists:
|
|
print(_("Error: %s") % sys.exc_info()[1])
|
|
return(1)
|
|
|
|
self.list(deployment_list=[deployment])
|
|
if do_use:
|
|
self.use(deployment["uuid"])
|
|
|
|
@cliutils.args("--deployment", dest="deployment", type=str,
|
|
metavar="<uuid>", required=False,
|
|
help="UUID or name of the deployment.")
|
|
@envutils.with_default_deployment()
|
|
@plugins.ensure_plugins_are_loaded
|
|
def recreate(self, deployment=None):
|
|
"""Destroy and create an existing deployment.
|
|
|
|
Unlike 'deployment destroy', the deployment database record
|
|
will not be deleted, so the deployment UUID stays the same.
|
|
|
|
:param deployment: UUID or name of the deployment
|
|
"""
|
|
api.Deployment.recreate(deployment)
|
|
|
|
@cliutils.args("--deployment", dest="deployment", type=str,
|
|
metavar="<uuid>", required=False,
|
|
help="UUID or name of the deployment.")
|
|
@envutils.with_default_deployment()
|
|
@plugins.ensure_plugins_are_loaded
|
|
def destroy(self, deployment=None):
|
|
"""Destroy existing deployment.
|
|
|
|
This will delete all containers, virtual machines, OpenStack
|
|
instances or Fuel clusters created during Rally deployment
|
|
creation. Also it will remove the deployment record from the
|
|
Rally database.
|
|
|
|
:param deployment: UUID or name of the deployment
|
|
"""
|
|
api.Deployment.destroy(deployment)
|
|
|
|
def list(self, deployment_list=None):
|
|
"""List existing deployments."""
|
|
|
|
headers = ["uuid", "created_at", "name", "status", "active"]
|
|
current_deployment = envutils.get_global("RALLY_DEPLOYMENT")
|
|
deployment_list = deployment_list or api.Deployment.list()
|
|
|
|
table_rows = []
|
|
if deployment_list:
|
|
for t in deployment_list:
|
|
r = [str(t[column]) for column in headers[:-1]]
|
|
r.append("" if t["uuid"] != current_deployment else "*")
|
|
table_rows.append(utils.Struct(**dict(zip(headers, r))))
|
|
cliutils.print_list(table_rows, headers,
|
|
sortby_index=headers.index("created_at"))
|
|
else:
|
|
print(_("There are no deployments. "
|
|
"To create a new deployment, use:"
|
|
"\nrally deployment create"))
|
|
|
|
@cliutils.args("--deployment", dest="deployment", type=str,
|
|
metavar="<uuid>", required=False,
|
|
help="UUID or name of the deployment.")
|
|
@envutils.with_default_deployment()
|
|
@cliutils.suppress_warnings
|
|
def config(self, deployment=None):
|
|
"""Display configuration of the deployment.
|
|
|
|
Output is the configuration of the deployment in a
|
|
pretty-printed JSON format.
|
|
|
|
:param deployment: UUID or name of the deployment
|
|
"""
|
|
deploy = api.Deployment.get(deployment)
|
|
result = deploy["config"]
|
|
print(json.dumps(result, sort_keys=True, indent=4))
|
|
|
|
@cliutils.args("--deployment", dest="deployment", type=str,
|
|
metavar="<uuid>", required=False,
|
|
help="UUID or name of the deployment.")
|
|
@envutils.with_default_deployment()
|
|
def show(self, deployment=None):
|
|
"""Show the credentials of the deployment.
|
|
|
|
:param deployment: UUID or name of the deployment
|
|
"""
|
|
|
|
headers = ["auth_url", "username", "password", "tenant_name",
|
|
"region_name", "endpoint_type"]
|
|
table_rows = []
|
|
|
|
deployment = api.Deployment.get(deployment)
|
|
users = deployment["users"]
|
|
admin = deployment["admin"]
|
|
credentials = users + [admin] if admin else users
|
|
|
|
for ep in credentials:
|
|
data = ["***" if m == "password" else ep.get(m, "")
|
|
for m in headers]
|
|
table_rows.append(utils.Struct(**dict(zip(headers, data))))
|
|
cliutils.print_list(table_rows, headers)
|
|
|
|
@cliutils.args("--deployment", dest="deployment", type=str,
|
|
metavar="<uuid>", required=False,
|
|
help="UUID or name of the deployment.")
|
|
@envutils.with_default_deployment()
|
|
def check(self, deployment=None):
|
|
"""Check keystone authentication and list all available services.
|
|
|
|
:param deployment: UUID or name of the deployment
|
|
"""
|
|
headers = ["services", "type", "status"]
|
|
table_rows = []
|
|
try:
|
|
deployment = api.Deployment.get(deployment)
|
|
|
|
except exceptions.DeploymentNotFound:
|
|
print(_("Deployment %s is not found.") % deployment)
|
|
return(1)
|
|
|
|
try:
|
|
services = api.Deployment.check(deployment)
|
|
except keystone_exceptions.ConnectionRefused:
|
|
print(_("Unable to connect %s.") % deployment["admin"]["auth_url"])
|
|
return(1)
|
|
|
|
except exceptions.InvalidArgumentsException:
|
|
data = ["keystone", "identity", "Error"]
|
|
table_rows.append(utils.Struct(**dict(zip(headers, data))))
|
|
print(_("Authentication Issues: %s.")
|
|
% sys.exc_info()[1])
|
|
return(1)
|
|
|
|
for serv_type, serv in services.items():
|
|
data = [serv, serv_type, "Available"]
|
|
table_rows.append(utils.Struct(**dict(zip(headers, data))))
|
|
print(_("keystone endpoints are valid and following"
|
|
" services are available:"))
|
|
cliutils.print_list(table_rows, headers)
|
|
if "__unknown__" in services.values():
|
|
print(_(
|
|
"NOTE: '__unknown__' service name means that Keystone service "
|
|
"catalog doesn't return name for this service and Rally can "
|
|
"not identify service by its type. BUT you still can use "
|
|
"such services with api_versions context, specifying type of "
|
|
"service (execute `rally plugin show api_versions` for more "
|
|
"details)."))
|
|
|
|
def _update_openrc_deployment_file(self, deployment, credential):
|
|
openrc_path = os.path.expanduser("~/.rally/openrc-%s" % deployment)
|
|
with open(openrc_path, "w+") as env_file:
|
|
env_file.write("export OS_AUTH_URL='%(auth_url)s'\n"
|
|
"export OS_USERNAME='%(username)s'\n"
|
|
"export OS_PASSWORD='%(password)s'\n"
|
|
"export OS_TENANT_NAME='%(tenant_name)s'\n"
|
|
% credential)
|
|
if credential.get("region_name"):
|
|
env_file.write("export OS_REGION_NAME='%s'\n" %
|
|
credential["region_name"])
|
|
if credential.get("endpoint"):
|
|
env_file.write("export OS_ENDPOINT='%s'\n" %
|
|
credential["endpoint"])
|
|
if credential.get("https_cacert"):
|
|
env_file.write("export OS_CACERT='%s'\n" %
|
|
credential["https_cacert"])
|
|
if re.match(r"^/v3/?$", parse.urlparse(
|
|
credential["auth_url"]).path) is not None:
|
|
env_file.write("export OS_USER_DOMAIN_NAME='%s'\n"
|
|
"export OS_PROJECT_DOMAIN_NAME='%s'\n" %
|
|
(credential["user_domain_name"],
|
|
credential["project_domain_name"]))
|
|
expanded_path = os.path.expanduser("~/.rally/openrc")
|
|
if os.path.exists(expanded_path):
|
|
os.remove(expanded_path)
|
|
os.symlink(openrc_path, expanded_path)
|
|
|
|
@cliutils.args("--deployment", dest="deployment", type=str,
|
|
metavar="<uuid>", required=False,
|
|
help="UUID or name of a deployment.")
|
|
def use(self, deployment):
|
|
"""Set active deployment.
|
|
|
|
:param deployment: UUID or name of the deployment
|
|
"""
|
|
try:
|
|
deployment = api.Deployment.get(deployment)
|
|
print("Using deployment: %s" % deployment["uuid"])
|
|
|
|
fileutils.update_globals_file("RALLY_DEPLOYMENT",
|
|
deployment["uuid"])
|
|
self._update_openrc_deployment_file(
|
|
deployment["uuid"],
|
|
deployment["admin"] or deployment["users"][0])
|
|
print ("~/.rally/openrc was updated\n\nHINTS:\n"
|
|
"* To get your cloud resources, run:\n\t"
|
|
"rally show [flavors|images|keypairs|networks|secgroups]\n"
|
|
"\n* To use standard OpenStack clients, set up your env by "
|
|
"running:\n\tsource ~/.rally/openrc\n"
|
|
" OpenStack clients are now configured, e.g run:\n\t"
|
|
"glance image-list")
|
|
except exceptions.DeploymentNotFound:
|
|
print("Deployment %s is not found." % deployment)
|
|
return 1
|