Adds a separate resource registration recipe

In order to make chef-client runs simpler and faster, we break
out a separate registration recipe that registers users, tenants,
services, endpoints, roles, etc with the Keystone server. This
recipe only needs to be in the run_list of one controller node
instead of all of them, which currently have the server recipe
in their runlists.

Removes the admin password/tenant/username stuff from the
credentials and register Chef providers and just replaces that
round-trip request to Keystone with the secret bootstrap token,
which should speed up the registration requests substantially.
This commit is contained in:
Jay Pipes
2013-03-19 13:23:56 -04:00
parent 4063006023
commit ee90e6d416
7 changed files with 174 additions and 236 deletions

View File

@@ -4,10 +4,11 @@ maintainer_email "matt@opscode.com"
license "Apache 2.0" license "Apache 2.0"
description "The OpenStack Identity service Keystone." description "The OpenStack Identity service Keystone."
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version "2012.2.0" version "2012.2.1"
recipe "keystone::db", "Configures database for use with keystone" recipe "keystone::db", "Configures database for use with keystone"
recipe "keystone::server", "Installs and Configures Keystone Service" recipe "keystone::server", "Installs and Configures Keystone Service"
recipe "keystone::registration", "Adds user, tenant, role and endpoint records to Keystone"
%w{ ubuntu fedora redhat centos }.each do |os| %w{ ubuntu fedora redhat centos }.each do |os|
supports os supports os

View File

@@ -183,42 +183,9 @@ def _http_get resource, path
end end
# Returns a token for use by a Keystone Admin user when
# issuing requests to the Keystone Admin API
def _get_admin_token auth_admin_uri, admin_tenant_name, admin_user, admin_password
# Construct a HTTP object from the supplied URI pointing to the
# Keystone Admin API endpoint.
uri = ::URI.parse(auth_admin_uri)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
path = _path uri, "tokens"
payload = Hash.new
payload['auth'] = Hash.new
payload['auth']['passwordCredentials'] = Hash.new
payload['auth']['passwordCredentials']['username'] = admin_user
payload['auth']['passwordCredentials']['password'] = admin_password
payload['auth']['tenantName'] = admin_tenant_name
req = Net::HTTP::Post.new(path)
req.add_field 'Content-type', 'application/json'
req.add_field 'user-agent', 'Chef keystone_register admin_token'
req.body = JSON.generate(payload)
resp = http.request req
if resp.is_a?(Net::HTTPOK)
data = JSON.parse resp.body
token = data['access']['token']['id']
else
Chef::Log.error("Unable to get admin token.")
Chef::Log.error("Response Code: #{resp.code}")
Chef::Log.error("Response Message: #{resp.message}")
end
end
# Constructs the request object with all the requisite headers added # Constructs the request object with all the requisite headers added
def _build_request resource, request def _build_request resource, request
admin_token = _get_admin_token resource.auth_uri, resource.admin_tenant_name, resource.admin_user, resource.admin_password admin_token = resource.bootstrap_token
request.add_field 'X-Auth-Token', admin_token request.add_field 'X-Auth-Token', admin_token
request.add_field 'Content-type', 'application/json' request.add_field 'Content-type', 'application/json'
request.add_field 'user-agent', 'Chef keystone_credentials' request.add_field 'user-agent', 'Chef keystone_credentials'

View File

@@ -423,7 +423,7 @@ end
# Short-cut for returning an Net::HTTP::Post to a path on the admin API endpoint. # Short-cut for returning an Net::HTTP::Post to a path on the admin API endpoint.
# Headers and admin token validation are already performed. All # Headers and bootstrap token validation are already performed. All
# the caller needs to do is call http.request, supplying the returned object # the caller needs to do is call http.request, supplying the returned object
def _http_post resource, path def _http_post resource, path
uri = ::URI.parse(resource.auth_uri) uri = ::URI.parse(resource.auth_uri)
@@ -434,7 +434,7 @@ end
# Short-cut for returning an Net::HTTP::Put to a path on the admin API endpoint. # Short-cut for returning an Net::HTTP::Put to a path on the admin API endpoint.
# Headers and admin token validation are already performed. All # Headers and bootstrap token validation are already performed. All
# the caller needs to do is call http.request, supplying the returned object # the caller needs to do is call http.request, supplying the returned object
def _http_put resource, path def _http_put resource, path
uri = ::URI.parse(resource.auth_uri) uri = ::URI.parse(resource.auth_uri)
@@ -454,44 +454,9 @@ def _http_get resource, path
_build_request resource, request _build_request resource, request
end end
# Returns a token for use by a Keystone Admin user when
# issuing requests to the Keystone Admin API
def _get_admin_token auth_admin_uri, admin_tenant_name, admin_user, admin_password
# Construct a HTTP object from the supplied URI pointing to the
# Keystone Admin API endpoint.
uri = ::URI.parse(auth_admin_uri)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
path = _path uri, "tokens"
payload = Hash.new
payload['auth'] = Hash.new
payload['auth']['passwordCredentials'] = Hash.new
payload['auth']['passwordCredentials']['username'] = admin_user
payload['auth']['passwordCredentials']['password'] = admin_password
payload['auth']['tenantName'] = admin_tenant_name
req = Net::HTTP::Post.new(path)
req.add_field 'Content-type', 'application/json'
req.add_field 'user-agent', 'Chef keystone_register admin_token'
req.body = JSON.generate(payload)
resp = http.request req
if resp.is_a?(Net::HTTPOK)
data = JSON.parse resp.body
token = data['access']['token']['id']
else
Chef::Log.error("Unable to get admin token.")
Chef::Log.error("Response Code: #{resp.code}")
Chef::Log.error("Response Message: #{resp.message}")
Chef::Log.error("Response Body: #{resp.body}")
end
end
# Constructs the request object with all the requisite headers added # Constructs the request object with all the requisite headers added
def _build_request resource, request def _build_request resource, request
admin_token = _get_admin_token resource.auth_uri, resource.admin_tenant_name, resource.admin_user, resource.admin_password admin_token = resource.bootstrap_token
request.add_field 'X-Auth-Token', admin_token request.add_field 'X-Auth-Token', admin_token
request.add_field 'Content-type', 'application/json' request.add_field 'Content-type', 'application/json'
request.add_field 'user-agent', 'Chef keystone_register' request.add_field 'user-agent', 'Chef keystone_register'

166
recipes/registration.rb Normal file
View File

@@ -0,0 +1,166 @@
#
# Cookbook Name:: keystone
# Recipe:: setup
#
# Copyright 2012, Rackspace US, Inc.
# Copyright 2012, Opscode, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require "uri"
class ::Chef::Recipe
include ::Openstack
end
identity_admin_endpoint = endpoint "identity-admin"
identity_endpoint = endpoint "identity-api"
admin_tenant_name = node["keystone"]["admin_tenant_name"]
admin_user = node["keystone"]["admin_user"]
admin_pass = user_password node["keystone"]["admin_user"]
auth_uri = ::URI.decode identity_admin_endpoint.to_s
bootstrap_token = secret "secrets", "keystone_bootstrap_token"
# We need to bootstrap the keystone admin user so that calls
# to keystone_register will succeed, since those provider calls
# use the admin tenant/user/pass to get an admin token.
bash "bootstrap-keystone-admin" do
# A shortcut bootstrap command was added to python-keystoneclient
# in early Grizzly timeframe... but we need to do all the commands
# here manually since the python-keystoneclient package included
# in CloudArchive (for now) doesn't have it...
insecure = node["openstack"]["auth"]["validate_certs"] ? "" : " --insecure"
base_ks_cmd = "keystone#{insecure} --endpoint=#{auth_uri} --token=#{bootstrap_token}"
code <<-EOF
set -x
function get_id () {
echo `"$@" | grep ' id ' | awk '{print $4}'`
}
#{base_ks_cmd} tenant-list | grep #{admin_tenant_name}
if [[ $? -eq 1 ]]; then
ADMIN_TENANT=$(get_id #{base_ks_cmd} tenant-create --name=#{admin_tenant_name})
else
ADMIN_TENANT=$(#{base_ks_cmd} tenant-list | grep #{admin_tenant_name} | awk '{print $2}')
fi
#{base_ks_cmd} role-list | grep admin
if [[ $? -eq 1 ]]; then
ADMIN_ROLE=$(get_id #{base_ks_cmd} role-create --name=admin)
else
ADMIN_ROLE=$(#{base_ks_cmd} role-list | grep admin | awk '{print $2}')
fi
#{base_ks_cmd} user-list | grep #{admin_user}
if [[ $? -eq 1 ]]; then
ADMIN_USER=$(get_id #{base_ks_cmd} user-create --name=#{admin_user} --pass="#{admin_pass}" --email=#{admin_user}@example.com)
else
ADMIN_USER=$(#{base_ks_cmd} user-list | grep #{admin_user} | awk '{print $2}')
fi
#{base_ks_cmd} user-role-list --user-id=$ADMIN_USER --tenant-id=$ADMIN_TENANT | grep admin
if [[ $? -eq 1 ]]; then
#{base_ks_cmd} user-role-add --user-id $ADMIN_USER --role-id $ADMIN_ROLE --tenant-id $ADMIN_TENANT
fi
exit 0
EOF
end
node["keystone"]["tenants"].each do |tenant_name|
## Add openstack tenant ##
keystone_register "Register '#{tenant_name}' Tenant" do
auth_uri auth_uri
bootstrap_token bootstrap_token
tenant_name tenant_name
tenant_description "#{tenant_name} Tenant"
tenant_enabled "true" # Not required as this is the default
action :create_tenant
end
end
node["keystone"]["roles"].each do |role_key|
keystone_register "Register '#{role_key.to_s}' Role" do
auth_uri auth_uri
bootstrap_token bootstrap_token
role_name role_key
action :create_role
end
end
node["keystone"]["users"].each do |username, user_info|
keystone_register "Register '#{username}' User" do
auth_uri auth_uri
bootstrap_token bootstrap_token
user_name username
user_pass user_info["password"]
tenant_name user_info["default_tenant"]
user_enabled "true" # Not required as this is the default
action :create_user
end
user_info["roles"].each do |rolename, tenant_list|
tenant_list.each do |tenantname|
keystone_register "Grant '#{rolename}' Role to '#{username}' User in '#{tenantname}' Tenant" do
auth_uri auth_uri
bootstrap_token bootstrap_token
user_name username
role_name rolename
tenant_name tenantname
action :grant_role
end
end
end
end
keystone_register "Register Identity Service" do
auth_uri auth_uri
bootstrap_token bootstrap_token
service_name "keystone"
service_type "identity"
service_description "Keystone Identity Service"
action :create_service
end
node.set["keystone"]["adminURL"] = identity_admin_endpoint.to_s
node.set["keystone"]["internalURL"] = identity_endpoint.to_s
node.set["keystone"]["publicURL"] = identity_endpoint.to_s
Chef::Log.info "Keystone AdminURL: #{identity_admin_endpoint.to_s}"
Chef::Log.info "Keystone InternalURL: #{identity_endpoint.to_s}"
Chef::Log.info "Keystone PublicURL: #{identity_endpoint.to_s}"
keystone_register "Register Identity Endpoint" do
auth_uri auth_uri
bootstrap_token bootstrap_token
service_type "identity"
endpoint_region node["keystone"]["region"]
endpoint_adminurl node["keystone"]["adminURL"]
endpoint_internalurl node["keystone"]["adminURL"]
endpoint_publicurl node["keystone"]["publicURL"]
action :create_endpoint
end
node["keystone"]["users"].each do |username, user_info|
keystone_credentials "Create EC2 credentials for '#{username}' user" do
auth_uri auth_uri
bootstrap_token bootstrap_token
user_name username
tenant_name user_info["default_tenant"]
end
end

View File

@@ -95,11 +95,6 @@ ec2_endpoint = endpoint "compute-ec2-api"
image_endpoint = endpoint "image-api" image_endpoint = endpoint "image-api"
volume_endpoint = endpoint "volume-api" volume_endpoint = endpoint "volume-api"
admin_tenant_name = node["keystone"]["admin_tenant_name"]
admin_user = node["keystone"]["admin_user"]
admin_pass = user_password node["keystone"]["admin_user"]
auth_uri = ::URI.decode identity_admin_endpoint.to_s
db_user = node["keystone"]["db"]["username"] db_user = node["keystone"]["db"]["username"]
db_pass = db_password "keystone" db_pass = db_password "keystone"
sql_connection = db_uri("identity", db_user, db_pass) sql_connection = db_uri("identity", db_user, db_pass)
@@ -156,155 +151,3 @@ end
# sync db after keystone.conf is generated # sync db after keystone.conf is generated
execute "keystone-manage db_sync" # idempotent execute "keystone-manage db_sync" # idempotent
# We need to bootstrap the keystone admin user so that calls
# to keystone_register will succeed, since those provider calls
# use the admin tenant/user/pass to get an admin token.
bash "bootstrap-keystone-admin" do
# A shortcut bootstrap command was added to python-keystoneclient
# in early Grizzly timeframe... but we need to do all the commands
# here manually since the python-keystoneclient package included
# in CloudArchive (for now) doesn't have it...
#command "keystone bootstrap --os-token=#{bootstrap_token} --user-name=#{admin_user} --tenant-name=#{admin_tenant_name} --pass=#{admin_pass}"
insecure = node["openstack"]["auth"]["validate_certs"] ? "" : " --insecure"
base_ks_cmd = "keystone#{insecure} --endpoint=#{auth_uri} --token=#{bootstrap_token}"
code <<-EOF
set -x
function get_id () {
echo `"$@" | grep ' id ' | awk '{print $4}'`
}
#{base_ks_cmd} tenant-list | grep #{admin_tenant_name}
if [[ $? -eq 1 ]]; then
ADMIN_TENANT=$(get_id #{base_ks_cmd} tenant-create --name=#{admin_tenant_name})
else
ADMIN_TENANT=$(#{base_ks_cmd} tenant-list | grep #{admin_tenant_name} | awk '{print $2}')
fi
#{base_ks_cmd} role-list | grep admin
if [[ $? -eq 1 ]]; then
ADMIN_ROLE=$(get_id #{base_ks_cmd} role-create --name=admin)
else
ADMIN_ROLE=$(#{base_ks_cmd} role-list | grep admin | awk '{print $2}')
fi
#{base_ks_cmd} user-list | grep #{admin_user}
if [[ $? -eq 1 ]]; then
ADMIN_USER=$(get_id #{base_ks_cmd} user-create --name=#{admin_user} --pass="#{admin_pass}" --email=#{admin_user}@example.com)
else
ADMIN_USER=$(#{base_ks_cmd} user-list | grep #{admin_user} | awk '{print $2}')
fi
#{base_ks_cmd} user-role-list --user-id=$ADMIN_USER --tenant-id=$ADMIN_TENANT | grep admin
if [[ $? -eq 1 ]]; then
#{base_ks_cmd} user-role-add --user-id $ADMIN_USER --role-id $ADMIN_ROLE --tenant-id $ADMIN_TENANT
fi
exit 0
EOF
end
#TODO(shep): this should probably be derived from keystone.users hash keys
node["keystone"]["tenants"].each do |tenant_name|
## Add openstack tenant ##
keystone_register "Register '#{tenant_name}' Tenant" do
auth_uri auth_uri
admin_user admin_user
admin_tenant_name admin_tenant_name
admin_password admin_pass
tenant_name tenant_name
tenant_description "#{tenant_name} Tenant"
tenant_enabled "true" # Not required as this is the default
action :create_tenant
end
end
## Add Roles ##
node["keystone"]["roles"].each do |role_key|
keystone_register "Register '#{role_key.to_s}' Role" do
auth_uri auth_uri
admin_user admin_user
admin_tenant_name admin_tenant_name
admin_password admin_pass
role_name role_key
action :create_role
end
end
node["keystone"]["users"].each do |username, user_info|
keystone_register "Register '#{username}' User" do
auth_uri auth_uri
admin_user admin_user
admin_tenant_name admin_tenant_name
admin_password admin_pass
user_name username
user_pass user_info["password"]
tenant_name user_info["default_tenant"]
user_enabled "true" # Not required as this is the default
action :create_user
end
user_info["roles"].each do |rolename, tenant_list|
tenant_list.each do |tenantname|
keystone_register "Grant '#{rolename}' Role to '#{username}' User in '#{tenantname}' Tenant" do
auth_uri auth_uri
admin_user admin_user
admin_tenant_name admin_tenant_name
admin_password admin_pass
user_name username
role_name rolename
tenant_name tenantname
action :grant_role
end
end
end
end
## Add Services ##
keystone_register "Register Identity Service" do
auth_uri auth_uri
admin_user admin_user
admin_tenant_name admin_tenant_name
admin_password admin_pass
service_name "keystone"
service_type "identity"
service_description "Keystone Identity Service"
action :create_service
end
## Add Endpoints ##
node.set["keystone"]["adminURL"] = identity_admin_endpoint.to_s
node.set["keystone"]["internalURL"] = identity_endpoint.to_s
node.set["keystone"]["publicURL"] = identity_endpoint.to_s
Chef::Log.info "Keystone AdminURL: #{identity_admin_endpoint.to_s}"
Chef::Log.info "Keystone InternalURL: #{identity_admin_endpoint.to_s}"
Chef::Log.info "Keystone PublicURL: #{identity_endpoint.to_s}"
keystone_register "Register Identity Endpoint" do
auth_uri auth_uri
admin_user admin_user
admin_tenant_name admin_tenant_name
admin_password admin_pass
service_type "identity"
endpoint_region node["keystone"]["region"]
endpoint_adminurl node["keystone"]["adminURL"]
endpoint_internalurl node["keystone"]["adminURL"]
endpoint_publicurl node["keystone"]["publicURL"]
action :create_endpoint
end
node["keystone"]["users"].each do |username, user_info|
keystone_credentials "Create EC2 credentials for '#{username}' user" do
auth_uri auth_uri
admin_user admin_user
admin_tenant_name admin_tenant_name
admin_password admin_pass
user_name username
tenant_name user_info["default_tenant"]
end
end

View File

@@ -27,9 +27,7 @@ def initialize(*args)
end end
attribute :auth_uri, :kind_of => String attribute :auth_uri, :kind_of => String
attribute :admin_user, :kind_of => String attribute :bootstrap_token, :kind_of => String
attribute :admin_password, :kind_of => String
attribute :admin_tenant_name, :kind_of => String
attribute :tenant_name, :kind_of => String attribute :tenant_name, :kind_of => String
attribute :user_name, :kind_of => String attribute :user_name, :kind_of => String

View File

@@ -27,9 +27,7 @@ def initialize(*args)
end end
attribute :auth_uri, :kind_of => String attribute :auth_uri, :kind_of => String
attribute :admin_user, :kind_of => String attribute :bootstrap_token, :kind_of => String
attribute :admin_password, :kind_of => String
attribute :admin_tenant_name, :kind_of => String
# Used by both :create_service and :create_endpoint # Used by both :create_service and :create_endpoint
attribute :service_type, :kind_of => String, :equal_to => [ "image", "identity", "compute", "storage", "ec2", "volume", "object-store" ] attribute :service_type, :kind_of => String, :equal_to => [ "image", "identity", "compute", "storage", "ec2", "volume", "object-store" ]