initial pull from rcbops-cookbooks/glance, monitoring removed
This commit is contained in:
86
README.md
86
README.md
@@ -1,17 +1,98 @@
|
||||
Description
|
||||
===========
|
||||
Placeholder for the OpenStack Image service `Glance` as part of the OpenStack `Essex` reference architecture using Chef. The http://github.com/opscode/openstack-chef-repo will contain documentation for using this cookbook in the context of a full OpenStack deployment.
|
||||
|
||||
Sharing the Nova database will be preferred for simplicity.
|
||||
This cookbook installs the OpenStack Image service **Glance** as part of the OpenStack **Essex** reference deployment Chef for OpenStack. The http://github.com/opscode/openstack-chef-repo contains documentation for using this cookbook in the context of a full OpenStack deployment. Glance is installed from packages, optionally populating the repository with default images.
|
||||
|
||||
http://glance.openstack.org/
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
Chef 0.10.0 or higher required (for Chef environment use)
|
||||
|
||||
Platform
|
||||
--------
|
||||
|
||||
* Ubuntu-12.04
|
||||
* Fedora-17
|
||||
|
||||
Cookbooks
|
||||
---------
|
||||
|
||||
The following cookbooks are dependencies:
|
||||
|
||||
* database
|
||||
* mysql
|
||||
* keystone
|
||||
* osops-utils
|
||||
|
||||
Recipes
|
||||
=======
|
||||
|
||||
default
|
||||
-------
|
||||
-Includes recipes `api`, `registry`
|
||||
|
||||
api
|
||||
------
|
||||
-Installs the glance-api server
|
||||
|
||||
registry
|
||||
--------
|
||||
-Includes recipe `mysql:client`
|
||||
-Installs the glance-registry server
|
||||
|
||||
Attributes
|
||||
==========
|
||||
|
||||
* `glance["db"]["name"]` - Name of glance database
|
||||
* `glance["db"]["user"]` - Username for glance database access
|
||||
* `glance["db"]["password"]` - Password for glance database access
|
||||
* `glance["api"]["ip_address"]` - IP address to use for communicating with the glance API
|
||||
* `glance["api"]["bind_address"]` - IP address for the glance API to bind to
|
||||
* `glance["api"]["port"]` - Port for the glance API to bind to
|
||||
* `glance["api"]["adminURL"]` - Used when registering image endpoint with keystone
|
||||
* `glance["api"]["internalURL"]` - Used when registering image endpoint with keystone
|
||||
* `glance["api"]["publicURL"]` - Used when registering image endpoint with keystone
|
||||
* `glance["registry"]["ip_address"]` - IP address to use for communicating with the glance registry
|
||||
* `glance["registry"]["bind_address"]` - IP address for the glance registry to bind to
|
||||
* `glance["registry"]["port"]` - IP address for the glance port to bind to
|
||||
* `glance["service_tenant_name"]` - Tenant name used by glance when interacting with keystone - used in the API and registry paste.ini files
|
||||
* `glance["service_user"]` - User name used by glance when interacting with keystone - used in the API and registry paste.ini files
|
||||
* `glance["service_pass"]` - User password used by glance when interacting with keystone - used in the API and registry paste.ini files
|
||||
* `glance["service_role"]` - User role used by glance when interacting with keystone - used in the API and registry paste.ini files
|
||||
* `glance["image_upload"]` - Toggles whether to automatically upload images in the `glance["images"]` array
|
||||
* `glance["images"]` - Default list of images to upload to the glance repository as part of the install
|
||||
* `glance["image]["<imagename>"]` - URL location of the <imagename> image. There can be multiple instances of this line to define multiple imagess (eg natty, maverick, fedora17 etc)
|
||||
--- example `glance["image]["natty"]` - "http://c250663.r63.cf1.rackcdn.com/ubuntu-11.04-server-uec-amd64-multinic.tar.gz"
|
||||
* `glance["api"]["default_store"]` - Toggles the backend storage type. Currently supported is "file" and "swift"
|
||||
* `glance["api"]["swift"]["store_container"] - Set the container used by glance to store images and snapshots. Defaults to "glance"
|
||||
* `glance["api"]["swift"]["store_large_object_size"] - Set the size at which glance starts to chunnk files. Defaults to "200" MB
|
||||
* `glance["api"]["swift"]["store_large_object_chunk_size"] - Set the chunk size for glance. Defaults to "200" MB
|
||||
|
||||
Templates
|
||||
=========
|
||||
|
||||
* `glance-api-paste.ini.erb` - Paste config for glance-api middleware
|
||||
* `glance-api.conf.erb` - Config file for glance-api server
|
||||
* `glance-registry-paste.ini.erb` - Paste config for glance-registry middleware
|
||||
* `glance-registry.conf.erb` - Config file for glance-registry server
|
||||
* `glance-scrubber.conf.erb` - Config file for glance image scrubber service
|
||||
* `policy.json.erb` - Configuration of ACLs for glance API server
|
||||
|
||||
License and Author
|
||||
==================
|
||||
|
||||
Author:: Justin Shepherd (<justin.shepherd@rackspace.com>)
|
||||
Author:: Jason Cannavale (<jason.cannavale@rackspace.com>)
|
||||
Author:: Ron Pedde (<ron.pedde@rackspace.com>)
|
||||
Author:: Joseph Breu (<joseph.breu@rackspace.com>)
|
||||
Author:: William Kelly (<william.kelly@rackspace.com>)
|
||||
Author:: Darren Birkett (<darren.birkett@rackspace.co.uk>)
|
||||
Author:: Evan Callicoat (<evan.callicoat@rackspace.com>)
|
||||
Author:: Matt Ray (<matt@opscode.com>)
|
||||
|
||||
Copyright 2012 Rackspace, Inc.
|
||||
Copyright 2012 Opscode, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -25,4 +106,3 @@ 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.
|
||||
|
||||
|
||||
79
attributes/default.rb
Normal file
79
attributes/default.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
#
|
||||
# Cookbook Name:: glance
|
||||
# Attributes:: glance
|
||||
#
|
||||
# Copyright 2009, Rackspace Hosting, 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.
|
||||
#
|
||||
|
||||
########################################################################
|
||||
# Toggles - These can be overridden at the environment level
|
||||
default["enable_monit"] = false # OS provides packages
|
||||
default["developer_mode"] = true # we want secure passwords by default
|
||||
########################################################################
|
||||
|
||||
default["glance"]["services"]["api"]["scheme"] = "http"
|
||||
default["glance"]["services"]["api"]["network"] = "public"
|
||||
default["glance"]["services"]["api"]["port"] = 9292
|
||||
default["glance"]["services"]["api"]["path"] = "/v1"
|
||||
|
||||
default["glance"]["services"]["registry"]["scheme"] = "http"
|
||||
default["glance"]["services"]["registry"]["network"] = "public"
|
||||
default["glance"]["services"]["registry"]["port"] = 9191
|
||||
default["glance"]["services"]["registry"]["path"] = "/v1"
|
||||
|
||||
default["glance"]["db"]["name"] = "glance"
|
||||
default["glance"]["db"]["username"] = "glance"
|
||||
|
||||
# TODO: These may need to be glance-registry specific.. and looked up by glance-api
|
||||
default["glance"]["service_tenant_name"] = "service"
|
||||
default["glance"]["service_user"] = "glance"
|
||||
default["glance"]["service_role"] = "admin"
|
||||
default["glance"]["api"]["default_store"] = "file"
|
||||
default["glance"]["api"]["swift"]["store_container"] = "glance"
|
||||
default["glance"]["api"]["swift"]["store_large_object_size"] = "200"
|
||||
default["glance"]["api"]["swift"]["store_large_object_chunk_size"] = "200"
|
||||
|
||||
default["glance"]["image_upload"] = false
|
||||
default["glance"]["images"] = [ "tty" ]
|
||||
default["glance"]["image"]["oneiric"] = "http://c250663.r63.cf1.rackcdn.com/ubuntu-11.10-server-uec-amd64-multinic.tar.gz"
|
||||
default["glance"]["image"]["natty"] = "http://c250663.r63.cf1.rackcdn.com/ubuntu-11.04-server-uec-amd64-multinic.tar.gz"
|
||||
default["glance"]["image"]["maverick"] = "http://c250663.r63.cf1.rackcdn.com/ubuntu-10.10-server-uec-amd64-multinic.tar.gz"
|
||||
#default["glance"]["image"]["tty"] = "http://smoser.brickies.net/ubuntu/ttylinux-uec/ttylinux-uec-amd64-12.1_2.6.35-22_1.tar.gz"
|
||||
default["glance"]["image"]["tty"] = "http://c250663.r63.cf1.rackcdn.com/ttylinux.tgz"
|
||||
default["glance"]["image"]["cirros"] = "https://launchpadlibrarian.net/83305869/cirros-0.3.0-x86_64-uec.tar.gz"
|
||||
|
||||
# logging attribute
|
||||
default["glance"]["syslog"]["use"] = true
|
||||
default["glance"]["syslog"]["facility"] = "LOG_LOCAL2"
|
||||
|
||||
# platform-specific settings
|
||||
case platform
|
||||
when "fedora"
|
||||
default["glance"]["platform"] = {
|
||||
"mysql_python_packages" => [ "MySQL-python" ],
|
||||
"glance_packages" => [ "openstack-glance", "openstack-swift" ],
|
||||
"glance_api_service" => "openstack-glance-api",
|
||||
"glance_registry_service" => "openstack-glance-registry",
|
||||
"package_overrides" => ""
|
||||
}
|
||||
when "ubuntu"
|
||||
default["glance"]["platform"] = {
|
||||
"mysql_python_packages" => [ "python-mysqldb" ],
|
||||
"glance_packages" => [ "glance", "python-swift" ],
|
||||
"glance_api_service" => "glance-api",
|
||||
"glance_registry_service" => "glance-registry",
|
||||
"package_overrides" => "-o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef'"
|
||||
}
|
||||
end
|
||||
116
files/default/glance_plugin.py
Normal file
116
files/default/glance_plugin.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from glance.client import V1Client
|
||||
from glance.common import exception
|
||||
|
||||
import collectd
|
||||
|
||||
global NAME, OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_AUTH_URL, OS_AUTH_STRATEGY, VERBOSE_LOGGING
|
||||
|
||||
OS_USERNAME = "username"
|
||||
OS_PASSWORD = "password"
|
||||
OS_TENANT_NAME = "tenantname"
|
||||
OS_AUTH_URL = "http://localhost:5000/v2.0"
|
||||
OS_AUTH_STRATEGY = "keystone"
|
||||
VERBOSE_LOGGING = False
|
||||
|
||||
def get_stats(user, passwd, tenant, url, host=None):
|
||||
creds = {"username": user, "password": passwd, "tenant": tenant,"auth_url": url, "strategy": OS_AUTH_STRATEGY}
|
||||
client = V1Client(host,creds=creds)
|
||||
try:
|
||||
image_list = client.get_images_detailed()
|
||||
except exception.NotAuthenticated:
|
||||
msg = "Client credentials appear to be invalid"
|
||||
raise exception.ClientConnectionError(msg)
|
||||
else:
|
||||
# TODO(shep): this needs to be rewritten more inline with the keystone|nova plugins
|
||||
data = dict()
|
||||
data["count"] = int(len(image_list))
|
||||
data["bytes"] = 0
|
||||
data["snapshot.count"] = 0
|
||||
data["snapshot.bytes"] = 0
|
||||
data["tenant"] = dict()
|
||||
for image in image_list:
|
||||
data["bytes"] += int(image["size"])
|
||||
if "image_type" in image["properties"] and image["properties"]["image_type"] == "snapshot":
|
||||
data["snapshot.count"] += 1
|
||||
data["snapshot.bytes"] += int(image["size"])
|
||||
uuid = str(image["owner"])
|
||||
if uuid in data["tenant"]:
|
||||
data["tenant"][uuid]["count"] += 1
|
||||
data["tenant"][uuid]["bytes"] += int(image["size"])
|
||||
if "image_type" in image["properties"] and image["properties"]["image_type"] == "snapshot":
|
||||
data["tenant"][uuid]["snapshot.count"] += 1
|
||||
data["tenant"][uuid]["snapshot.bytes"] += int(image["size"])
|
||||
else:
|
||||
data["tenant"][uuid] = dict()
|
||||
data["tenant"][uuid]["count"] = 1
|
||||
data["tenant"][uuid]["bytes"] = int(image["size"])
|
||||
data["tenant"][uuid]["snapshot.count"] = 0
|
||||
data["tenant"][uuid]["snapshot.bytes"] = 0
|
||||
if "image_type" in image["properties"] and image["properties"]["image_type"] == "snapshot":
|
||||
data["tenant"][uuid]["snapshot.count"] += 1
|
||||
data["tenant"][uuid]["snapshot.bytes"] += int(image["size"])
|
||||
# debug
|
||||
#for key in data.keys():
|
||||
# if key == "tenant":
|
||||
# for uuid in data[key].keys():
|
||||
# for field in data[key][uuid]:
|
||||
# print "glance.images.tenant.%s.%s : %i" % (uuid, field, data[key][uuid][field])
|
||||
# else:
|
||||
# print "glance.images.%s : %i" % (key, data[key])
|
||||
##########
|
||||
return data
|
||||
|
||||
def configure_callback(conf):
|
||||
"""Received configuration information"""
|
||||
global OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_AUTH_URL
|
||||
for node in conf.children:
|
||||
if node.key == "Username":
|
||||
OS_USERNAME = node.values[0]
|
||||
elif node.key == "Password":
|
||||
OS_PASSWORD = node.values[0]
|
||||
elif node.key == "TenantName":
|
||||
OS_TENANT_NAME = node.values[0]
|
||||
elif node.key == "AuthURL":
|
||||
OS_AUTH_URL = node.values[0]
|
||||
elif node.key == "Verbose":
|
||||
VERBOSE_LOGGING = node.values[0]
|
||||
else:
|
||||
logger("warn", "Unknown config key: %s" % node.key)
|
||||
|
||||
def read_callback():
|
||||
logger("verb", "read_callback")
|
||||
info = get_stats(OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_AUTH_URL)
|
||||
|
||||
if not info:
|
||||
logger("err", "No information received")
|
||||
return
|
||||
|
||||
for key in info.keys():
|
||||
if key == "tenant":
|
||||
for uuid in info[key].keys():
|
||||
for field in info[key][uuid]:
|
||||
logger('verb', 'Dispatching glance.images.tenant.%s.%s : %i' % (uuid, field, int(info[key][uuid][field])))
|
||||
path = 'glance.images.%s.%s' % (uuid, field)
|
||||
val = collectd.Values(plugin=path)
|
||||
val.type = 'gauge'
|
||||
val.values = [int(info[key][uuid][field])]
|
||||
val.dispatch()
|
||||
else:
|
||||
logger('verb', 'Dispatching %s : %i' % (key, int(info[key])))
|
||||
path = 'glance.images.%s' % (key)
|
||||
val = collectd.Values(plugin=path)
|
||||
val.type = 'gauge'
|
||||
val.values = [int(info[key])]
|
||||
val.dispatch()
|
||||
|
||||
def logger(t, msg):
|
||||
if t == 'err':
|
||||
collectd.error('%s: %s' % (NAME, msg))
|
||||
if t == 'warn':
|
||||
collectd.warning('%s: %s' % (NAME, msg))
|
||||
elif t == 'verb' and VERBOSE_LOGGING == True:
|
||||
collectd.info('%s: %s' % (NAME, msg))
|
||||
|
||||
collectd.register_config(configure_callback)
|
||||
collectd.warning("Initializing glance plugin")
|
||||
collectd.register_read(read_callback)
|
||||
21
metadata.rb
21
metadata.rb
@@ -1,6 +1,15 @@
|
||||
maintainer "Opscode, Inc."
|
||||
maintainer_email "matt@opscode.com"
|
||||
license "Apache 2.0"
|
||||
description "The OpenStack Image service Glance."
|
||||
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
|
||||
version "0.0.1"
|
||||
maintainer "Opscode, Inc."
|
||||
license "Apache 2.0"
|
||||
description "The Glance Image Registry and Delivery Service Glance"
|
||||
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
|
||||
version "5.0.0"
|
||||
recipe "glance::api", "Installs packages required for a glance api server"
|
||||
recipe "glance::registry", "Installs packages required for a glance registry server"
|
||||
|
||||
%w{ ubuntu fedora }.each do |os|
|
||||
supports os
|
||||
end
|
||||
|
||||
%w{ database keystone mysql osops-utils }.each do |dep|
|
||||
depends dep
|
||||
end
|
||||
|
||||
220
recipes/api.rb
Normal file
220
recipes/api.rb
Normal file
@@ -0,0 +1,220 @@
|
||||
#
|
||||
# Cookbook Name:: glance
|
||||
# Recipe:: api
|
||||
#
|
||||
# Copyright 2009-2012, Rackspace Hosting, 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.
|
||||
#
|
||||
include_recipe "glance::glance-rsyslog"
|
||||
|
||||
platform_options = node["glance"]["platform"]
|
||||
|
||||
package "curl" do
|
||||
action :upgrade
|
||||
end
|
||||
|
||||
package "python-keystone" do
|
||||
action :install
|
||||
end
|
||||
|
||||
platform_options["glance_packages"].each do |pkg|
|
||||
package pkg do
|
||||
action :upgrade
|
||||
end
|
||||
end
|
||||
|
||||
service "glance-api" do
|
||||
service_name platform_options["glance_api_service"]
|
||||
supports :status => true, :restart => true
|
||||
action :enable
|
||||
end
|
||||
|
||||
# FIXME: this is broken. Joe, Wilk, fix this.
|
||||
template "/usr/share/pyshared/glance/store/swift.py" do
|
||||
source "swift.py"
|
||||
group "root"
|
||||
owner "root"
|
||||
mode "0644"
|
||||
only_if do platform?(%w{debian ubuntu}) end
|
||||
notifies :restart, resources(:service => "glance-api"), :immediately
|
||||
end
|
||||
|
||||
directory "/etc/glance" do
|
||||
action :create
|
||||
group "glance"
|
||||
owner "glance"
|
||||
mode "0700"
|
||||
end
|
||||
|
||||
# FIXME: seems like misfeature
|
||||
template "/etc/glance/policy.json" do
|
||||
source "policy.json.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
notifies :restart, resources(:service => "glance-api"), :immediately
|
||||
not_if do
|
||||
File.exists?("/etc/glance/policy.json")
|
||||
end
|
||||
end
|
||||
|
||||
rabbit_info = get_settings_by_role("rabbitmq-server", "rabbitmq") # FIXME: access
|
||||
|
||||
ks_admin_endpoint = get_access_endpoint("keystone", "keystone", "admin-api")
|
||||
ks_service_endpoint = get_access_endpoint("keystone", "keystone","service-api")
|
||||
keystone = get_settings_by_role("keystone", "keystone")
|
||||
glance = get_settings_by_role("glance-api", "glance")
|
||||
|
||||
registry_endpoint = get_access_endpoint("glance-registry", "glance", "registry")
|
||||
api_endpoint = get_bind_endpoint("glance", "api")
|
||||
|
||||
template "/etc/glance/glance-api.conf" do
|
||||
source "glance-api.conf.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
variables(
|
||||
"api_bind_address" => api_endpoint["host"],
|
||||
"api_bind_port" => api_endpoint["port"],
|
||||
"registry_ip_address" => registry_endpoint["host"],
|
||||
"registry_port" => registry_endpoint["port"],
|
||||
"use_syslog" => node["glance"]["syslog"]["use"],
|
||||
"log_facility" => node["glance"]["syslog"]["facility"],
|
||||
"rabbit_ipaddress" => rabbit_info["ipaddress"], #FIXME!
|
||||
"keystone_api_ipaddress" => ks_admin_endpoint["host"],
|
||||
"keystone_service_port" => ks_service_endpoint["port"],
|
||||
"service_user" => glance["service_user"],
|
||||
"service_pass" => glance["service_pass"],
|
||||
"service_tenant_name" => glance["service_tenant_name"],
|
||||
"default_store" => glance["api"]["default_store"],
|
||||
"swift_large_object_size" => glance["api"]["swift"]["store_large_object_size"],
|
||||
"swift_large_object_chunk_size" => glance["api"]["swift"]["store_large_object_chunk_size"],
|
||||
"swift_store_container" => glance["api"]["swift"]["store_container"]
|
||||
)
|
||||
notifies :restart, resources(:service => "glance-api"), :immediately
|
||||
end
|
||||
|
||||
template "/etc/glance/glance-api-paste.ini" do
|
||||
source "glance-api-paste.ini.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
variables(
|
||||
"keystone_api_ipaddress" => ks_admin_endpoint["host"],
|
||||
"keystone_service_port" => ks_service_endpoint["port"],
|
||||
"keystone_admin_port" => ks_admin_endpoint["port"],
|
||||
"keystone_admin_token" => keystone["admin_token"],
|
||||
"service_tenant_name" => node["glance"]["service_tenant_name"],
|
||||
"service_user" => node["glance"]["service_user"],
|
||||
"service_pass" => node["glance"]["service_pass"]
|
||||
)
|
||||
notifies :restart, resources(:service => "glance-api"), :immediately
|
||||
end
|
||||
|
||||
template "/etc/glance/glance-scrubber.conf" do
|
||||
source "glance-scrubber.conf.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
variables(
|
||||
"registry_ip_address" => registry_endpoint["host"],
|
||||
"registry_port" => registry_endpoint["port"]
|
||||
)
|
||||
end
|
||||
|
||||
template "/etc/glance/glance-scrubber-paste.ini" do
|
||||
source "glance-scrubber-paste.ini.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
end
|
||||
|
||||
# Register Image Service
|
||||
keystone_register "Register Image Service" do
|
||||
auth_host ks_admin_endpoint["host"]
|
||||
auth_port ks_admin_endpoint["port"]
|
||||
auth_protocol ks_admin_endpoint["scheme"]
|
||||
api_ver ks_admin_endpoint["path"]
|
||||
auth_token keystone["admin_token"]
|
||||
service_name "glance"
|
||||
service_type "image"
|
||||
service_description "Glance Image Service"
|
||||
action :create_service
|
||||
end
|
||||
|
||||
# Register Image Endpoint
|
||||
keystone_register "Register Image Endpoint" do
|
||||
auth_host ks_admin_endpoint["host"]
|
||||
auth_port ks_admin_endpoint["port"]
|
||||
auth_protocol ks_admin_endpoint["scheme"]
|
||||
api_ver ks_admin_endpoint["path"]
|
||||
auth_token keystone["admin_token"]
|
||||
service_type "image"
|
||||
endpoint_region "RegionOne"
|
||||
endpoint_adminurl api_endpoint["uri"]
|
||||
endpoint_internalurl api_endpoint["uri"]
|
||||
endpoint_publicurl api_endpoint["uri"]
|
||||
action :create_endpoint
|
||||
end
|
||||
|
||||
if node["glance"]["image_upload"]
|
||||
# TODO(breu): the environment needs to be derived from a search
|
||||
# TODO(shep): this whole bit is super dirty.. and needs some love.
|
||||
node["glance"]["images"].each do |img|
|
||||
Chef::Log.info("Checking to see if #{img.to_s}-image should be uploaded.")
|
||||
|
||||
keystone_admin_user = keystone["admin_user"]
|
||||
keystone_admin_password = keystone["users"][keystone_admin_user]["password"]
|
||||
keystone_tenant = keystone["users"][keystone_admin_user]["default_tenant"]
|
||||
|
||||
|
||||
bash "default image setup for #{img.to_s}" do
|
||||
cwd "/tmp"
|
||||
user "root"
|
||||
environment ({"OS_USERNAME" => keystone_admin_user,
|
||||
"OS_PASSWORD" => keystone_admin_password,
|
||||
"OS_TENANT_NAME" => keystone_tenant,
|
||||
"OS_AUTH_URL" => ks_admin_endpoint["uri"]})
|
||||
code <<-EOH
|
||||
set -e
|
||||
set -x
|
||||
mkdir -p images/#{img.to_s}
|
||||
cd images/#{img.to_s}
|
||||
|
||||
curl #{node["glance"]["image"][img.to_sym]} | tar -zx
|
||||
image_name=$(basename #{node["glance"]["image"][img]} .tar.gz)
|
||||
|
||||
image_name=${image_name%-multinic}
|
||||
|
||||
kernel_file=$(ls *vmlinuz-virtual | head -n1)
|
||||
if [ ${#kernel_file} -eq 0 ]; then
|
||||
kernel_file=$(ls *vmlinuz | head -n1)
|
||||
fi
|
||||
|
||||
ramdisk=$(ls *-initrd | head -n1)
|
||||
if [ ${#ramdisk} -eq 0 ]; then
|
||||
ramdisk=$(ls *-loader | head -n1)
|
||||
fi
|
||||
|
||||
kernel=$(ls *.img | head -n1)
|
||||
|
||||
kid=$(glance --silent-upload add name="${image_name}-kernel" is_public=true disk_format=aki container_format=aki < ${kernel_file} | cut -d: -f2 | sed 's/ //')
|
||||
rid=$(glance --silent-upload add name="${image_name}-initrd" is_public=true disk_format=ari container_format=ari < ${ramdisk} | cut -d: -f2 | sed 's/ //')
|
||||
glance --silent-upload add name="#{img.to_s}-image" is_public=true disk_format=ami container_format=ami kernel_id=$kid ramdisk_id=$rid < ${kernel}
|
||||
EOH
|
||||
not_if "glance -f -I #{keystone_admin_user} -K #{keystone_admin_password} -T #{keystone_tenant} -N #{ks_admin_endpoint["uri"]} index | grep #{img.to_s}-image"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,6 +2,7 @@
|
||||
# Cookbook Name:: glance
|
||||
# Recipe:: default
|
||||
#
|
||||
# Copyright 2009-2012, Rackspace Hosting, Inc.
|
||||
# Copyright 2012, Opscode, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -17,3 +18,5 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
include_recipe "glance::registry"
|
||||
include_recipe "glance::api"
|
||||
|
||||
31
recipes/glance-rsyslog.rb
Normal file
31
recipes/glance-rsyslog.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# Cookbook Name:: glance
|
||||
# Recipe:: default
|
||||
#
|
||||
# Copyright 2009, Rackspace Hosting, 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.
|
||||
#
|
||||
|
||||
if node["glance"]["syslog"]["use"]
|
||||
template "/etc/rsyslog.d/22-glance.conf" do
|
||||
source "22-glance.conf.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
variables(
|
||||
"use_syslog" => node["glance"]["syslog"]["use"],
|
||||
"log_facility" => node["glance"]["syslog"]["facility"]
|
||||
)
|
||||
end
|
||||
end
|
||||
174
recipes/registry.rb
Normal file
174
recipes/registry.rb
Normal file
@@ -0,0 +1,174 @@
|
||||
#
|
||||
# Cookbook Name:: glance
|
||||
# Recipe:: registry
|
||||
#
|
||||
# Copyright 2009, Rackspace Hosting, 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.
|
||||
#
|
||||
|
||||
::Chef::Recipe.send(:include, Opscode::OpenSSL::Password)
|
||||
include_recipe "mysql::client"
|
||||
include_recipe "glance::glance-rsyslog"
|
||||
|
||||
platform_options = node["glance"]["platform"]
|
||||
|
||||
# Allow for using a well known db password
|
||||
if node["developer_mode"]
|
||||
node.set_unless['glance']['db']['password'] = "glance"
|
||||
else
|
||||
node.set_unless['glance']['db']['password'] = secure_password
|
||||
end
|
||||
|
||||
# Set a secure keystone service password
|
||||
node.set_unless['glance']['service_pass'] = secure_password
|
||||
|
||||
package "python-keystone" do
|
||||
action :install
|
||||
end
|
||||
|
||||
ks_admin_endpoint = get_access_endpoint("keystone", "keystone", "admin-api")
|
||||
ks_service_endpoint = get_access_endpoint("keystone", "keystone", "service-api")
|
||||
keystone = get_settings_by_role("keystone", "keystone")
|
||||
|
||||
registry_endpoint = get_bind_endpoint("glance", "registry")
|
||||
|
||||
#creates db and user
|
||||
#returns connection info
|
||||
#defined in osops-utils/libraries
|
||||
mysql_info = create_db_and_user("mysql",
|
||||
node["glance"]["db"]["name"],
|
||||
node["glance"]["db"]["username"],
|
||||
node["glance"]["db"]["password"])
|
||||
|
||||
package "curl" do
|
||||
action :install
|
||||
end
|
||||
|
||||
platform_options["mysql_python_packages"].each do |pkg|
|
||||
package pkg do
|
||||
action :install
|
||||
end
|
||||
end
|
||||
|
||||
platform_options["glance_packages"].each do |pkg|
|
||||
package pkg do
|
||||
action :upgrade
|
||||
end
|
||||
end
|
||||
|
||||
service "glance-registry" do
|
||||
service_name platform_options["glance_registry_service"]
|
||||
supports :status => true, :restart => true
|
||||
action :enable
|
||||
end
|
||||
|
||||
execute "glance-manage db_sync" do
|
||||
command "sudo -u glance glance-manage db_sync"
|
||||
action :nothing
|
||||
notifies :restart, resources(:service => "glance-registry"), :immediately
|
||||
end
|
||||
|
||||
# Having to manually version the database because of Ubuntu bug
|
||||
# https://bugs.launchpad.net/ubuntu/+source/glance/+bug/981111
|
||||
execute "glance-manage version_control" do
|
||||
command "sudo -u glance glance-manage version_control 0"
|
||||
action :nothing
|
||||
not_if "sudo -u glance glance-manage db_version"
|
||||
notifies :run, resources(:execute => "glance-manage db_sync"), :immediately
|
||||
end
|
||||
|
||||
file "/var/lib/glance/glance.sqlite" do
|
||||
action :delete
|
||||
end
|
||||
|
||||
# Register Service Tenant
|
||||
keystone_register "Register Service Tenant" do
|
||||
auth_host ks_admin_endpoint["host"]
|
||||
auth_port ks_admin_endpoint["port"]
|
||||
auth_protocol ks_admin_endpoint["scheme"]
|
||||
api_ver ks_admin_endpoint["path"]
|
||||
auth_token keystone["admin_token"]
|
||||
tenant_name node["glance"]["service_tenant_name"]
|
||||
tenant_description "Service Tenant"
|
||||
tenant_enabled "true" # Not required as this is the default
|
||||
action :create_tenant
|
||||
end
|
||||
|
||||
# Register Service User
|
||||
keystone_register "Register Service User" do
|
||||
auth_host ks_admin_endpoint["host"]
|
||||
auth_port ks_admin_endpoint["port"]
|
||||
auth_protocol ks_admin_endpoint["scheme"]
|
||||
api_ver ks_admin_endpoint["path"]
|
||||
auth_token keystone["admin_token"]
|
||||
tenant_name node["glance"]["service_tenant_name"]
|
||||
user_name node["glance"]["service_user"]
|
||||
user_pass node["glance"]["service_pass"]
|
||||
user_enabled "true" # Not required as this is the default
|
||||
action :create_user
|
||||
end
|
||||
|
||||
## Grant Admin role to Service User for Service Tenant ##
|
||||
keystone_register "Grant 'admin' Role to Service User for Service Tenant" do
|
||||
auth_host ks_admin_endpoint["host"]
|
||||
auth_port ks_admin_endpoint["port"]
|
||||
auth_protocol ks_admin_endpoint["scheme"]
|
||||
api_ver ks_admin_endpoint["path"]
|
||||
auth_token keystone["admin_token"]
|
||||
tenant_name node["glance"]["service_tenant_name"]
|
||||
user_name node["glance"]["service_user"]
|
||||
role_name node["glance"]["service_role"]
|
||||
action :grant_role
|
||||
end
|
||||
|
||||
directory "/etc/glance" do
|
||||
action :create
|
||||
group "glance"
|
||||
owner "glance"
|
||||
mode "0700"
|
||||
end
|
||||
|
||||
template "/etc/glance/glance-registry.conf" do
|
||||
source "glance-registry.conf.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
variables(
|
||||
"registry_bind_address" => registry_endpoint["host"],
|
||||
"registry_port" => registry_endpoint["port"],
|
||||
"db_ip_address" => mysql_info["bind_address"],
|
||||
"db_user" => node["glance"]["db"]["username"],
|
||||
"db_password" => node["glance"]["db"]["password"],
|
||||
"db_name" => node["glance"]["db"]["name"],
|
||||
"use_syslog" => node["glance"]["syslog"]["use"],
|
||||
"log_facility" => node["glance"]["syslog"]["facility"]
|
||||
)
|
||||
notifies :run, resources(:execute => "glance-manage version_control"), :immediately
|
||||
end
|
||||
|
||||
template "/etc/glance/glance-registry-paste.ini" do
|
||||
source "glance-registry-paste.ini.erb"
|
||||
owner "root"
|
||||
group "root"
|
||||
mode "0644"
|
||||
variables(
|
||||
"keystone_api_ipaddress" => ks_admin_endpoint["host"],
|
||||
"keystone_service_port" => ks_service_endpoint["port"],
|
||||
"keystone_admin_port" => ks_admin_endpoint["port"],
|
||||
"service_tenant_name" => node["glance"]["service_tenant_name"],
|
||||
"service_user" => node["glance"]["service_user"],
|
||||
"service_pass" => node["glance"]["service_pass"]
|
||||
)
|
||||
notifies :restart, resources(:service => "glance-registry"), :immediately
|
||||
end
|
||||
7
templates/default/22-glance.conf.erb
Normal file
7
templates/default/22-glance.conf.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
$DirGroup adm
|
||||
$DirCreateMode 0755
|
||||
$FileGroup adm
|
||||
|
||||
$template GlanceLog, "/var/log/glance/glance.log"
|
||||
|
||||
local2.* -?GlanceLog
|
||||
76
templates/default/glance-api-paste.ini.erb
Normal file
76
templates/default/glance-api-paste.ini.erb
Normal file
@@ -0,0 +1,76 @@
|
||||
# Default minimal pipeline
|
||||
[pipeline:glance-api]
|
||||
pipeline = versionnegotiation context apiv1app
|
||||
|
||||
# Use the following pipeline for keystone auth
|
||||
# i.e. in glance-api.conf:
|
||||
# [paste_deploy]
|
||||
# flavor = keystone
|
||||
#
|
||||
[pipeline:glance-api-keystone]
|
||||
pipeline = versionnegotiation authtoken context apiv1app
|
||||
|
||||
# Use the following pipeline to enable transparent caching of image files
|
||||
# i.e. in glance-api.conf:
|
||||
# [paste_deploy]
|
||||
# flavor = caching
|
||||
#
|
||||
[pipeline:glance-api-caching]
|
||||
pipeline = versionnegotiation context cache apiv1app
|
||||
|
||||
# Use the following pipeline for keystone auth with caching
|
||||
# i.e. in glance-api.conf:
|
||||
# [paste_deploy]
|
||||
# flavor = keystone+caching
|
||||
#
|
||||
[pipeline:glance-api-keystone+caching]
|
||||
pipeline = versionnegotiation authtoken context cache apiv1app
|
||||
|
||||
# Use the following pipeline to enable the Image Cache Management API
|
||||
# i.e. in glance-api.conf:
|
||||
# [paste_deploy]
|
||||
# flavor = cachemanagement
|
||||
#
|
||||
[pipeline:glance-api-cachemanagement]
|
||||
pipeline = versionnegotiation context cache cachemanage apiv1app
|
||||
|
||||
# Use the following pipeline for keystone auth with cache management
|
||||
# i.e. in glance-api.conf:
|
||||
# [paste_deploy]
|
||||
# flavor = keystone+cachemanagement
|
||||
#
|
||||
[pipeline:glance-api-keystone+cachemanagement]
|
||||
pipeline = versionnegotiation authtoken context cache cachemanage apiv1app
|
||||
|
||||
[app:apiv1app]
|
||||
paste.app_factory = glance.common.wsgi:app_factory
|
||||
glance.app_factory = glance.api.v1.router:API
|
||||
|
||||
[filter:versionnegotiation]
|
||||
paste.filter_factory = glance.common.wsgi:filter_factory
|
||||
glance.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter
|
||||
|
||||
[filter:cache]
|
||||
paste.filter_factory = glance.common.wsgi:filter_factory
|
||||
glance.filter_factory = glance.api.middleware.cache:CacheFilter
|
||||
|
||||
[filter:cachemanage]
|
||||
paste.filter_factory = glance.common.wsgi:filter_factory
|
||||
glance.filter_factory = glance.api.middleware.cache_manage:CacheManageFilter
|
||||
|
||||
[filter:context]
|
||||
paste.filter_factory = glance.common.wsgi:filter_factory
|
||||
glance.filter_factory = glance.common.context:ContextMiddleware
|
||||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystone.middleware.auth_token:filter_factory
|
||||
service_protocol = http
|
||||
service_host = <%= @keystone_api_ipaddress %>
|
||||
service_port = <%= @keystone_service_port %>
|
||||
auth_host = <%= @keystone_api_ipaddress %>
|
||||
auth_port = <%= @keystone_admin_port %>
|
||||
auth_protocol = http
|
||||
auth_uri = http://<%= @keystone_api_ipaddress %>:<%= @keystone_service_port %>/
|
||||
admin_tenant_name = <%= @service_tenant_name %>
|
||||
admin_user = <%= @service_user %>
|
||||
admin_password = <%= @service_pass %>
|
||||
244
templates/default/glance-api.conf.erb
Normal file
244
templates/default/glance-api.conf.erb
Normal file
@@ -0,0 +1,244 @@
|
||||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
# Which backend store should Glance use by default is not specified
|
||||
# in a request to add a new image to Glance? Default: 'file'
|
||||
# Available choices are 'file', 'swift', and 's3'
|
||||
default_store = file
|
||||
|
||||
# Address to bind the API server
|
||||
bind_host = <%= @api_bind_address %>
|
||||
|
||||
# Port the bind the API server to
|
||||
bind_port = <%= @api_bind_port %>
|
||||
|
||||
# Log to this file. Make sure you do not set the same log
|
||||
# file for both the API and registry servers!
|
||||
log_file = /var/log/glance/api.log
|
||||
|
||||
# Backlog requests when creating socket
|
||||
backlog = 4096
|
||||
|
||||
# Number of Glance API worker processes to start.
|
||||
# On machines with more than one CPU increasing this value
|
||||
# may improve performance (especially if using SSL with
|
||||
# compression turned on). It is typically recommended to set
|
||||
# this value to the number of CPUs present on your machine.
|
||||
workers = 0
|
||||
|
||||
# Role used to identify an authenticated user as administrator
|
||||
#admin_role = admin
|
||||
|
||||
# ================= Syslog Options ============================
|
||||
|
||||
# Send logs to syslog (/dev/log) instead of to file specified
|
||||
# by `log_file`
|
||||
use_syslog = <%= @use_syslog %>
|
||||
|
||||
<% if @use_syslog == true %>
|
||||
# Facility to use. If unset defaults to LOG_USER.
|
||||
syslog_log_facility = <%= @log_facility %>
|
||||
<% else %>
|
||||
# syslog_log_facility = LOG_USER
|
||||
<% end %>
|
||||
|
||||
# ================= SSL Options ===============================
|
||||
|
||||
# Certificate file to use when starting API server securely
|
||||
# cert_file = /path/to/certfile
|
||||
|
||||
# Private key file to use when starting API server securely
|
||||
# key_file = /path/to/keyfile
|
||||
|
||||
# ================= Security Options ==========================
|
||||
|
||||
# AES key for encrypting store 'location' metadata, including
|
||||
# -- if used -- Swift or S3 credentials
|
||||
# Should be set to a random string of length 16, 24 or 32 bytes
|
||||
# metadata_encryption_key = <16, 24 or 32 char registry metadata key>
|
||||
|
||||
# ============ Registry Options ===============================
|
||||
|
||||
# Address to find the registry server
|
||||
registry_host = <%= @registry_ip_address %>
|
||||
|
||||
# Port the registry server is listening on
|
||||
registry_port = <%= @registry_port %>
|
||||
|
||||
# What protocol to use when connecting to the registry server?
|
||||
# Set to https for secure HTTP communication
|
||||
registry_client_protocol = http
|
||||
|
||||
# The path to the key file to use in SSL connections to the
|
||||
# registry server, if any. Alternately, you may set the
|
||||
# GLANCE_CLIENT_KEY_FILE environ variable to a filepath of the key file
|
||||
# registry_client_key_file = /path/to/key/file
|
||||
|
||||
# The path to the cert file to use in SSL connections to the
|
||||
# registry server, if any. Alternately, you may set the
|
||||
# GLANCE_CLIENT_CERT_FILE environ variable to a filepath of the cert file
|
||||
# registry_client_cert_file = /path/to/cert/file
|
||||
|
||||
# The path to the certifying authority cert file to use in SSL connections
|
||||
# to the registry server, if any. Alternately, you may set the
|
||||
# GLANCE_CLIENT_CA_FILE environ variable to a filepath of the CA cert file
|
||||
# registry_client_ca_file = /path/to/ca/file
|
||||
|
||||
# ============ Notification System Options =====================
|
||||
|
||||
# Notifications can be sent when images are create, updated or deleted.
|
||||
# There are three methods of sending notifications, logging (via the
|
||||
# log_file directive), rabbit (via a rabbitmq queue), qpid (via a Qpid
|
||||
# message queue), or noop (no notifications sent, the default)
|
||||
notifier_strategy = noop
|
||||
|
||||
# Configuration options if sending notifications via rabbitmq (these are
|
||||
# the defaults)
|
||||
rabbit_host = <%= @rabbit_ipaddress %>
|
||||
rabbit_port = 5672
|
||||
rabbit_use_ssl = false
|
||||
rabbit_userid = guest
|
||||
rabbit_password = guest
|
||||
rabbit_virtual_host = /
|
||||
rabbit_notification_exchange = glance
|
||||
rabbit_notification_topic = glance_notifications
|
||||
|
||||
# Configuration options if sending notifications via Qpid (these are
|
||||
# the defaults)
|
||||
qpid_notification_exchange = glance
|
||||
qpid_notification_topic = glance_notifications
|
||||
qpid_host = localhost
|
||||
qpid_port = 5672
|
||||
qpid_username =
|
||||
qpid_password =
|
||||
qpid_sasl_mechanisms =
|
||||
qpid_reconnect_timeout = 0
|
||||
qpid_reconnect_limit = 0
|
||||
qpid_reconnect_interval_min = 0
|
||||
qpid_reconnect_interval_max = 0
|
||||
qpid_reconnect_interval = 0
|
||||
qpid_heartbeat = 5
|
||||
# Set to 'ssl' to enable SSL
|
||||
qpid_protocol = tcp
|
||||
qpid_tcp_nodelay = True
|
||||
|
||||
# ============ Filesystem Store Options ========================
|
||||
|
||||
# Directory that the Filesystem backend store
|
||||
# writes image data to
|
||||
filesystem_store_datadir = /var/lib/glance/images/
|
||||
|
||||
# ============ Swift Store Options =============================
|
||||
|
||||
# Address where the Swift authentication service lives
|
||||
# Valid schemes are 'http://' and 'https://'
|
||||
# If no scheme specified, default to 'https://'
|
||||
swift_store_auth_address = 127.0.0.1:8080/v1.0/
|
||||
|
||||
# User to authenticate against the Swift authentication service
|
||||
# If you use Swift authentication service, set it to 'account':'user'
|
||||
# where 'account' is a Swift storage account and 'user'
|
||||
# is a user in that account
|
||||
swift_store_user = jdoe:jdoe
|
||||
|
||||
# Auth key for the user authenticating against the
|
||||
# Swift authentication service
|
||||
swift_store_key = a86850deb2742ec3cb41518e26aa2d89
|
||||
|
||||
# Container within the account that the account should use
|
||||
# for storing images in Swift
|
||||
swift_store_container = glance
|
||||
|
||||
# Do we create the container if it does not exist?
|
||||
swift_store_create_container_on_put = False
|
||||
|
||||
# What size, in MB, should Glance start chunking image files
|
||||
# and do a large object manifest in Swift? By default, this is
|
||||
# the maximum object size in Swift, which is 5GB
|
||||
swift_store_large_object_size = 5120
|
||||
|
||||
# When doing a large object manifest, what size, in MB, should
|
||||
# Glance write chunks to Swift? This amount of data is written
|
||||
# to a temporary disk buffer during the process of chunking
|
||||
# the image file, and the default is 200MB
|
||||
swift_store_large_object_chunk_size = 200
|
||||
|
||||
# Whether to use ServiceNET to communicate with the Swift storage servers.
|
||||
# (If you aren't RACKSPACE, leave this False!)
|
||||
#
|
||||
# To use ServiceNET for authentication, prefix hostname of
|
||||
# `swift_store_auth_address` with 'snet-'.
|
||||
# Ex. https://example.com/v1.0/ -> https://snet-example.com/v1.0/
|
||||
swift_enable_snet = False
|
||||
|
||||
# ============ S3 Store Options =============================
|
||||
|
||||
# Address where the S3 authentication service lives
|
||||
# Valid schemes are 'http://' and 'https://'
|
||||
# If no scheme specified, default to 'http://'
|
||||
s3_store_host = 127.0.0.1:8080/v1.0/
|
||||
|
||||
# User to authenticate against the S3 authentication service
|
||||
s3_store_access_key = <20-char AWS access key>
|
||||
|
||||
# Auth key for the user authenticating against the
|
||||
# S3 authentication service
|
||||
s3_store_secret_key = <40-char AWS secret key>
|
||||
|
||||
# Container within the account that the account should use
|
||||
# for storing images in S3. Note that S3 has a flat namespace,
|
||||
# so you need a unique bucket name for your glance images. An
|
||||
# easy way to do this is append your AWS access key to "glance".
|
||||
# S3 buckets in AWS *must* be lowercased, so remember to lowercase
|
||||
# your AWS access key if you use it in your bucket name below!
|
||||
s3_store_bucket = <lowercased 20-char aws access key>glance
|
||||
|
||||
# Do we create the bucket if it does not exist?
|
||||
s3_store_create_bucket_on_put = False
|
||||
|
||||
# When sending images to S3, the data will first be written to a
|
||||
# temporary buffer on disk. By default the platform's temporary directory
|
||||
# will be used. If required, an alternative directory can be specified here.
|
||||
# s3_store_object_buffer_dir = /path/to/dir
|
||||
|
||||
# ============ RBD Store Options =============================
|
||||
|
||||
# Ceph configuration file path
|
||||
# If using cephx authentication, this file should
|
||||
# include a reference to the right keyring
|
||||
# in a client.<USER> section
|
||||
rbd_store_ceph_conf = /etc/ceph/ceph.conf
|
||||
|
||||
# RADOS user to authenticate as (only applicable if using cephx)
|
||||
rbd_store_user = glance
|
||||
|
||||
# RADOS pool in which images are stored
|
||||
rbd_store_pool = images
|
||||
|
||||
# Images will be chunked into objects of this size (in megabytes).
|
||||
# For best performance, this should be a power of two
|
||||
rbd_store_chunk_size = 8
|
||||
|
||||
# ============ Delayed Delete Options =============================
|
||||
|
||||
# Turn on/off delayed delete
|
||||
delayed_delete = False
|
||||
|
||||
# Delayed delete time in seconds
|
||||
scrub_time = 43200
|
||||
|
||||
# Directory that the scrubber will use to remind itself of what to delete
|
||||
# Make sure this is also set in glance-scrubber.conf
|
||||
scrubber_datadir = /var/lib/glance/scrubber
|
||||
|
||||
# =============== Image Cache Options =============================
|
||||
|
||||
# Base directory that the Image Cache uses
|
||||
image_cache_dir = /var/lib/glance/image-cache/
|
||||
|
||||
[paste_deploy]
|
||||
flavor = keystone
|
||||
33
templates/default/glance-registry-paste.ini.erb
Normal file
33
templates/default/glance-registry-paste.ini.erb
Normal file
@@ -0,0 +1,33 @@
|
||||
# Default minimal pipeline
|
||||
[pipeline:glance-registry]
|
||||
pipeline = context registryapp
|
||||
|
||||
# Use the following pipeline for keystone auth
|
||||
# i.e. in glance-registry.conf:
|
||||
# [paste_deploy]
|
||||
# flavor = keystone
|
||||
#
|
||||
[pipeline:glance-registry-keystone]
|
||||
pipeline = authtoken context registryapp
|
||||
|
||||
[app:registryapp]
|
||||
paste.app_factory = glance.common.wsgi:app_factory
|
||||
glance.app_factory = glance.registry.api.v1:API
|
||||
|
||||
[filter:context]
|
||||
context_class = glance.registry.context.RequestContext
|
||||
paste.filter_factory = glance.common.wsgi:filter_factory
|
||||
glance.filter_factory = glance.common.context:ContextMiddleware
|
||||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystone.middleware.auth_token:filter_factory
|
||||
service_protocol = http
|
||||
service_host = <%= @keystone_api_ipaddress %>
|
||||
service_port = <%= @keystone_service_port %>
|
||||
auth_host = <%= @keystone_api_ipaddress %>
|
||||
auth_port = <%= @keystone_admin_port %>
|
||||
auth_protocol = http
|
||||
auth_uri = http://<%= @keystone_api_ipaddress %>:<%= @keystone_service_port %>/
|
||||
admin_tenant_name = <%= @service_tenant_name %>
|
||||
admin_user = <%= @service_user %>
|
||||
admin_password = <%= @service_pass %>
|
||||
65
templates/default/glance-registry.conf.erb
Normal file
65
templates/default/glance-registry.conf.erb
Normal file
@@ -0,0 +1,65 @@
|
||||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
# Address to bind the registry server
|
||||
bind_host = <%= @registry_bind_address %>
|
||||
|
||||
# Port the bind the registry server to
|
||||
bind_port = <%= @registry_port %>
|
||||
|
||||
# Log to this file. Make sure you do not set the same log
|
||||
# file for both the API and registry servers!
|
||||
log_file = /var/log/glance/registry.log
|
||||
|
||||
# Backlog requests when creating socket
|
||||
backlog = 4096
|
||||
|
||||
# SQLAlchemy connection string for the reference implementation
|
||||
# registry server. Any valid SQLAlchemy connection string is fine.
|
||||
# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
|
||||
sql_connection = mysql://<%= @db_user %>:<%= @db_password %>@<%= @db_ip_address %>/<%= @db_name %>
|
||||
|
||||
# Period in seconds after which SQLAlchemy should reestablish its connection
|
||||
# to the database.
|
||||
#
|
||||
# MySQL uses a default `wait_timeout` of 8 hours, after which it will drop
|
||||
# idle connections. This can result in 'MySQL Gone Away' exceptions. If you
|
||||
# notice this, you can lower this value to ensure that SQLAlchemy reconnects
|
||||
# before MySQL can drop the connection.
|
||||
sql_idle_timeout = 3600
|
||||
|
||||
# Limit the api to return `param_limit_max` items in a call to a container. If
|
||||
# a larger `limit` query param is provided, it will be reduced to this value.
|
||||
api_limit_max = 1000
|
||||
|
||||
# If a `limit` query param is not provided in an api request, it will
|
||||
# default to `limit_param_default`
|
||||
limit_param_default = 25
|
||||
|
||||
# ================= Syslog Options ============================
|
||||
|
||||
# Send logs to syslog (/dev/log) instead of to file specified
|
||||
# by `log_file`
|
||||
use_syslog = <%= @use_syslog %>
|
||||
|
||||
<% if @use_syslog == true %>
|
||||
# Facility to use. If unset defaults to LOG_USER.
|
||||
syslog_log_facility = <%= @log_facility %>
|
||||
<% else %>
|
||||
# syslog_log_facility = LOG_USER
|
||||
<% end %>
|
||||
|
||||
# ================= SSL Options ===============================
|
||||
|
||||
# Certificate file to use when starting registry server securely
|
||||
# cert_file = /path/to/certfile
|
||||
|
||||
# Private key file to use when starting registry server securely
|
||||
# key_file = /path/to/keyfile
|
||||
|
||||
[paste_deploy]
|
||||
flavor = keystone
|
||||
3
templates/default/glance-scrubber-paste.ini.erb
Normal file
3
templates/default/glance-scrubber-paste.ini.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
[app:glance-scrubber]
|
||||
paste.app_factory = glance.common.wsgi:app_factory
|
||||
glance.app_factory = glance.store.scrubber:Scrubber
|
||||
35
templates/default/glance-scrubber.conf.erb
Normal file
35
templates/default/glance-scrubber.conf.erb
Normal file
@@ -0,0 +1,35 @@
|
||||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
# Log to this file. Make sure you do not set the same log
|
||||
# file for both the API and registry servers!
|
||||
log_file = /var/log/glance/scrubber.log
|
||||
|
||||
# Send logs to syslog (/dev/log) instead of to file specified by `log_file`
|
||||
use_syslog = False
|
||||
|
||||
# Should we run our own loop or rely on cron/scheduler to run us
|
||||
daemon = False
|
||||
|
||||
# Loop time between checking the db for new items to schedule for delete
|
||||
wakeup_time = 300
|
||||
|
||||
# Directory that the scrubber will use to remind itself of what to delete
|
||||
# Make sure this is also set in glance-api.conf
|
||||
scrubber_datadir = /var/lib/glance/scrubber
|
||||
|
||||
# Only one server in your deployment should be designated the cleanup host
|
||||
cleanup_scrubber = False
|
||||
|
||||
# pending_delete items older than this time are candidates for cleanup
|
||||
cleanup_scrubber_time = 86400
|
||||
|
||||
# Address to find the registry server for cleanups
|
||||
registry_host = <%= @registry_ip_address %>
|
||||
|
||||
# Port the registry server is listening on
|
||||
registry_port = <%= @registry_port %>
|
||||
4
templates/default/policy.json.erb
Normal file
4
templates/default/policy.json.erb
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"default": [],
|
||||
"manage_image_cache": [["role:admin"]]
|
||||
}
|
||||
565
templates/default/swift.py
Normal file
565
templates/default/swift.py
Normal file
@@ -0,0 +1,565 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010-2011 OpenStack, LLC
|
||||
# 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.
|
||||
|
||||
"""Storage backend for SWIFT"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import hashlib
|
||||
import httplib
|
||||
import logging
|
||||
import math
|
||||
import urlparse
|
||||
|
||||
from glance.common import cfg
|
||||
from glance.common import exception
|
||||
import glance.store
|
||||
import glance.store.base
|
||||
import glance.store.location
|
||||
|
||||
try:
|
||||
from swift.common import client as swift_client
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
DEFAULT_CONTAINER = 'glance'
|
||||
DEFAULT_LARGE_OBJECT_SIZE = 5 * 1024 # 5GB
|
||||
DEFAULT_LARGE_OBJECT_CHUNK_SIZE = 200 # 200M
|
||||
ONE_MB = 1000 * 1024
|
||||
|
||||
logger = logging.getLogger('glance.store.swift')
|
||||
|
||||
|
||||
class StoreLocation(glance.store.location.StoreLocation):
|
||||
|
||||
"""
|
||||
Class describing a Swift URI. A Swift URI can look like any of
|
||||
the following:
|
||||
|
||||
swift://user:pass@authurl.com/container/obj-id
|
||||
swift://account:user:pass@authurl.com/container/obj-id
|
||||
swift+http://user:pass@authurl.com/container/obj-id
|
||||
swift+https://user:pass@authurl.com/container/obj-id
|
||||
|
||||
The swift+http:// URIs indicate there is an HTTP authentication URL.
|
||||
The default for Swift is an HTTPS authentication URL, so swift:// and
|
||||
swift+https:// are the same...
|
||||
"""
|
||||
|
||||
def process_specs(self):
|
||||
self.scheme = self.specs.get('scheme', 'swift+https')
|
||||
self.user = self.specs.get('user')
|
||||
self.key = self.specs.get('key')
|
||||
self.authurl = self.specs.get('authurl')
|
||||
self.container = self.specs.get('container')
|
||||
self.obj = self.specs.get('obj')
|
||||
|
||||
def _get_credstring(self):
|
||||
if self.user:
|
||||
return '%s:%s@' % (self.user, self.key)
|
||||
return ''
|
||||
|
||||
def get_uri(self):
|
||||
authurl = self.authurl
|
||||
if authurl.startswith('http://'):
|
||||
authurl = authurl[7:]
|
||||
elif authurl.startswith('https://'):
|
||||
authurl = authurl[8:]
|
||||
|
||||
credstring = self._get_credstring()
|
||||
authurl = authurl.strip('/')
|
||||
container = self.container.strip('/')
|
||||
obj = self.obj.strip('/')
|
||||
|
||||
return '%s://%s%s/%s/%s' % (self.scheme, credstring, authurl,
|
||||
container, obj)
|
||||
|
||||
def parse_uri(self, uri):
|
||||
"""
|
||||
Parse URLs. This method fixes an issue where credentials specified
|
||||
in the URL are interpreted differently in Python 2.6.1+ than prior
|
||||
versions of Python. It also deals with the peculiarity that new-style
|
||||
Swift URIs have where a username can contain a ':', like so:
|
||||
|
||||
swift://account:user:pass@authurl.com/container/obj
|
||||
"""
|
||||
# Make sure that URIs that contain multiple schemes, such as:
|
||||
# swift://user:pass@http://authurl.com/v1/container/obj
|
||||
# are immediately rejected.
|
||||
if uri.count('://') != 1:
|
||||
reason = _(
|
||||
"URI cannot contain more than one occurrence of a scheme."
|
||||
"If you have specified a URI like "
|
||||
"swift://user:pass@http://authurl.com/v1/container/obj"
|
||||
", you need to change it to use the swift+http:// scheme, "
|
||||
"like so: "
|
||||
"swift+http://user:pass@authurl.com/v1/container/obj"
|
||||
)
|
||||
raise exception.BadStoreUri(uri, reason)
|
||||
|
||||
pieces = urlparse.urlparse(uri)
|
||||
assert pieces.scheme in ('swift', 'swift+http', 'swift+https')
|
||||
self.scheme = pieces.scheme
|
||||
netloc = pieces.netloc
|
||||
path = pieces.path.lstrip('/')
|
||||
if netloc != '':
|
||||
# > Python 2.6.1
|
||||
if '@' in netloc:
|
||||
creds, netloc = netloc.split('@')
|
||||
else:
|
||||
creds = None
|
||||
else:
|
||||
# Python 2.6.1 compat
|
||||
# see lp659445 and Python issue7904
|
||||
if '@' in path:
|
||||
creds, path = path.split('@')
|
||||
else:
|
||||
creds = None
|
||||
netloc = path[0:path.find('/')].strip('/')
|
||||
path = path[path.find('/'):].strip('/')
|
||||
if creds:
|
||||
cred_parts = creds.split(':')
|
||||
|
||||
# User can be account:user, in which case cred_parts[0:2] will be
|
||||
# the account and user. Combine them into a single username of
|
||||
# account:user
|
||||
if len(cred_parts) == 1:
|
||||
reason = (_("Badly formed credentials '%(creds)s' in Swift "
|
||||
"URI") % locals())
|
||||
raise exception.BadStoreUri(uri, reason)
|
||||
elif len(cred_parts) == 3:
|
||||
user = ':'.join(cred_parts[0:2])
|
||||
else:
|
||||
user = cred_parts[0]
|
||||
key = cred_parts[-1]
|
||||
self.user = user
|
||||
self.key = key
|
||||
else:
|
||||
self.user = None
|
||||
path_parts = path.split('/')
|
||||
try:
|
||||
self.obj = path_parts.pop()
|
||||
self.container = path_parts.pop()
|
||||
if not netloc.startswith('http'):
|
||||
# push hostname back into the remaining to build full authurl
|
||||
path_parts.insert(0, netloc)
|
||||
self.authurl = '/'.join(path_parts)
|
||||
except IndexError:
|
||||
reason = _("Badly formed Swift URI")
|
||||
raise exception.BadStoreUri(uri, reason)
|
||||
|
||||
@property
|
||||
def swift_auth_url(self):
|
||||
"""
|
||||
Creates a fully-qualified auth url that the Swift client library can
|
||||
use. The scheme for the auth_url is determined using the scheme
|
||||
included in the `location` field.
|
||||
|
||||
HTTPS is assumed, unless 'swift+http' is specified.
|
||||
"""
|
||||
if self.scheme in ('swift+https', 'swift'):
|
||||
auth_scheme = 'https://'
|
||||
else:
|
||||
auth_scheme = 'http://'
|
||||
|
||||
full_url = ''.join([auth_scheme, self.authurl.rstrip("/"), "/"])
|
||||
return full_url
|
||||
|
||||
|
||||
class Store(glance.store.base.Store):
|
||||
"""An implementation of the swift backend adapter."""
|
||||
|
||||
EXAMPLE_URL = "swift://<USER>:<KEY>@<AUTH_ADDRESS>/<CONTAINER>/<FILE>"
|
||||
|
||||
CHUNKSIZE = 65536
|
||||
|
||||
opts = [
|
||||
cfg.BoolOpt('swift_enable_snet', default=False),
|
||||
cfg.StrOpt('swift_store_auth_address'),
|
||||
cfg.StrOpt('swift_store_user', secret=True),
|
||||
cfg.StrOpt('swift_store_key', secret=True),
|
||||
cfg.StrOpt('swift_store_auth_version', default='2'),
|
||||
cfg.StrOpt('swift_store_container',
|
||||
default=DEFAULT_CONTAINER),
|
||||
cfg.IntOpt('swift_store_large_object_size',
|
||||
default=DEFAULT_LARGE_OBJECT_SIZE),
|
||||
cfg.IntOpt('swift_store_large_object_chunk_size',
|
||||
default=DEFAULT_LARGE_OBJECT_CHUNK_SIZE),
|
||||
cfg.BoolOpt('swift_store_create_container_on_put', default=False),
|
||||
]
|
||||
|
||||
def configure(self):
|
||||
self.conf.register_opts(self.opts)
|
||||
self.snet = self.conf.swift_enable_snet
|
||||
self.auth_version = self._option_get('swift_store_auth_version')
|
||||
|
||||
def configure_add(self):
|
||||
"""
|
||||
Configure the Store to use the stored configuration options
|
||||
Any store that needs special configuration should implement
|
||||
this method. If the store was not able to successfully configure
|
||||
itself, it should raise `exception.BadStoreConfiguration`
|
||||
"""
|
||||
self.auth_address = self._option_get('swift_store_auth_address')
|
||||
self.user = self._option_get('swift_store_user')
|
||||
self.key = self._option_get('swift_store_key')
|
||||
self.container = self.conf.swift_store_container
|
||||
try:
|
||||
# The config file has swift_store_large_object_*size in MB, but
|
||||
# internally we store it in bytes, since the image_size parameter
|
||||
# passed to add() is also in bytes.
|
||||
self.large_object_size = \
|
||||
self.conf.swift_store_large_object_size * ONE_MB
|
||||
self.large_object_chunk_size = \
|
||||
self.conf.swift_store_large_object_chunk_size * ONE_MB
|
||||
except cfg.ConfigFileValueError, e:
|
||||
reason = _("Error in configuration conf: %s") % e
|
||||
logger.error(reason)
|
||||
raise exception.BadStoreConfiguration(store_name="swift",
|
||||
reason=reason)
|
||||
|
||||
self.scheme = 'swift+https'
|
||||
if self.auth_address.startswith('http://'):
|
||||
self.scheme = 'swift+http'
|
||||
self.full_auth_address = self.auth_address
|
||||
elif self.auth_address.startswith('https://'):
|
||||
self.full_auth_address = self.auth_address
|
||||
else: # Defaults https
|
||||
self.full_auth_address = 'https://' + self.auth_address
|
||||
self.full_auth_address = ''.join([self.full_auth_address.rstrip("/"), '/'])
|
||||
def get(self, location):
|
||||
"""
|
||||
Takes a `glance.store.location.Location` object that indicates
|
||||
where to find the image file, and returns a tuple of generator
|
||||
(for reading the image file) and image_size
|
||||
|
||||
:param location `glance.store.location.Location` object, supplied
|
||||
from glance.store.location.get_location_from_uri()
|
||||
:raises `glance.exception.NotFound` if image does not exist
|
||||
"""
|
||||
loc = location.store_location
|
||||
swift_conn = self._make_swift_connection(
|
||||
auth_url=loc.swift_auth_url, user=loc.user, key=loc.key)
|
||||
|
||||
try:
|
||||
(resp_headers, resp_body) = swift_conn.get_object(
|
||||
container=loc.container, obj=loc.obj,
|
||||
resp_chunk_size=self.CHUNKSIZE)
|
||||
except swift_client.ClientException, e:
|
||||
if e.http_status == httplib.NOT_FOUND:
|
||||
uri = location.get_store_uri()
|
||||
raise exception.NotFound(_("Swift could not find image at "
|
||||
"uri %(uri)s") % locals())
|
||||
else:
|
||||
raise
|
||||
|
||||
class ResponseIndexable(glance.store.Indexable):
|
||||
def another(self):
|
||||
try:
|
||||
return self.wrapped.next()
|
||||
except StopIteration:
|
||||
return ''
|
||||
|
||||
length = resp_headers.get('content-length')
|
||||
return (ResponseIndexable(resp_body, length), length)
|
||||
|
||||
def get_size(self, location):
|
||||
"""
|
||||
Takes a `glance.store.location.Location` object that indicates
|
||||
where to find the image file, and returns the image_size (or 0
|
||||
if unavailable)
|
||||
|
||||
:param location `glance.store.location.Location` object, supplied
|
||||
from glance.store.location.get_location_from_uri()
|
||||
"""
|
||||
loc = location.store_location
|
||||
swift_conn = self._make_swift_connection(
|
||||
auth_url=loc.swift_auth_url, user=loc.user, key=loc.key)
|
||||
|
||||
try:
|
||||
resp_headers = swift_conn.head_object(container=loc.container,
|
||||
obj=loc.obj)
|
||||
return resp_headers.get('content-length', 0)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def _make_swift_connection(self, auth_url, user, key):
|
||||
"""
|
||||
Creates a connection using the Swift client library.
|
||||
"""
|
||||
snet = self.snet
|
||||
auth_version = self.auth_version
|
||||
logger.debug(_("Creating Swift connection with "
|
||||
"(auth_address=%(auth_url)s, user=%(user)s, "
|
||||
"snet=%(snet)s, auth_version=%(auth_version)s)") %
|
||||
locals())
|
||||
return swift_client.Connection(
|
||||
authurl=auth_url, user=user, key=key, snet=snet,
|
||||
auth_version=auth_version)
|
||||
|
||||
def _option_get(self, param):
|
||||
result = getattr(self.conf, param)
|
||||
if not result:
|
||||
reason = (_("Could not find %(param)s in configuration "
|
||||
"options.") % locals())
|
||||
logger.error(reason)
|
||||
raise exception.BadStoreConfiguration(store_name="swift",
|
||||
reason=reason)
|
||||
return result
|
||||
|
||||
def add(self, image_id, image_file, image_size):
|
||||
"""
|
||||
Stores an image file with supplied identifier to the backend
|
||||
storage system and returns an `glance.store.ImageAddResult` object
|
||||
containing information about the stored image.
|
||||
|
||||
:param image_id: The opaque image identifier
|
||||
:param image_file: The image data to write, as a file-like object
|
||||
:param image_size: The size of the image data to write, in bytes
|
||||
|
||||
:retval `glance.store.ImageAddResult` object
|
||||
:raises `glance.common.exception.Duplicate` if the image already
|
||||
existed
|
||||
|
||||
Swift writes the image data using the scheme:
|
||||
``swift://<USER>:<KEY>@<AUTH_ADDRESS>/<CONTAINER>/<ID>`
|
||||
where:
|
||||
<USER> = ``swift_store_user``
|
||||
<KEY> = ``swift_store_key``
|
||||
<AUTH_ADDRESS> = ``swift_store_auth_address``
|
||||
<CONTAINER> = ``swift_store_container``
|
||||
<ID> = The id of the image being added
|
||||
|
||||
:note Swift auth URLs by default use HTTPS. To specify an HTTP
|
||||
auth URL, you can specify http://someurl.com for the
|
||||
swift_store_auth_address config option
|
||||
|
||||
:note Swift cannot natively/transparently handle objects >5GB
|
||||
in size. So, if the image is greater than 5GB, we write
|
||||
chunks of image data to Swift and then write an manifest
|
||||
to Swift that contains information about the chunks.
|
||||
This same chunking process is used by default for images
|
||||
of an unknown size, as pushing them directly to swift would
|
||||
fail if the image turns out to be greater than 5GB.
|
||||
"""
|
||||
swift_conn = self._make_swift_connection(
|
||||
auth_url=self.full_auth_address, user=self.user, key=self.key)
|
||||
|
||||
create_container_if_missing(self.container, swift_conn, self.conf)
|
||||
|
||||
obj_name = str(image_id)
|
||||
location = StoreLocation({'scheme': self.scheme,
|
||||
'container': self.container,
|
||||
'obj': obj_name,
|
||||
'authurl': self.auth_address,
|
||||
'user': self.user,
|
||||
'key': self.key})
|
||||
|
||||
logger.debug(_("Adding image object '%(obj_name)s' "
|
||||
"to Swift") % locals())
|
||||
try:
|
||||
if image_size > 0 and image_size < self.large_object_size:
|
||||
# Image size is known, and is less than large_object_size.
|
||||
# Send to Swift with regular PUT.
|
||||
obj_etag = swift_conn.put_object(self.container, obj_name,
|
||||
image_file,
|
||||
content_length=image_size)
|
||||
else:
|
||||
# Write the image into Swift in chunks.
|
||||
chunk_id = 1
|
||||
if image_size > 0:
|
||||
total_chunks = str(int(
|
||||
math.ceil(float(image_size) /
|
||||
float(self.large_object_chunk_size))))
|
||||
else:
|
||||
# image_size == 0 is when we don't know the size
|
||||
# of the image. This can occur with older clients
|
||||
# that don't inspect the payload size.
|
||||
logger.debug(_("Cannot determine image size. Adding as a "
|
||||
"segmented object to Swift."))
|
||||
total_chunks = '?'
|
||||
|
||||
checksum = hashlib.md5()
|
||||
combined_chunks_size = 0
|
||||
while True:
|
||||
chunk_size = self.large_object_chunk_size
|
||||
if image_size == 0:
|
||||
content_length = None
|
||||
else:
|
||||
left = image_size - combined_chunks_size
|
||||
if left == 0:
|
||||
break
|
||||
if chunk_size > left:
|
||||
chunk_size = left
|
||||
content_length = chunk_size
|
||||
|
||||
chunk_name = "%s-%05d" % (obj_name, chunk_id)
|
||||
reader = ChunkReader(image_file, checksum, chunk_size)
|
||||
chunk_etag = swift_conn.put_object(
|
||||
self.container, chunk_name, reader,
|
||||
content_length=content_length)
|
||||
bytes_read = reader.bytes_read
|
||||
logger.debug(_("Wrote chunk %(chunk_id)d/"
|
||||
"%(total_chunks)s of length %(bytes_read)d "
|
||||
"to Swift returning MD5 of content: "
|
||||
"%(chunk_etag)s")
|
||||
% locals())
|
||||
|
||||
if bytes_read == 0:
|
||||
# Delete the last chunk, because it's of zero size.
|
||||
# This will happen if image_size == 0.
|
||||
logger.debug(_("Deleting final zero-length chunk"))
|
||||
swift_conn.delete_object(self.container, chunk_name)
|
||||
break
|
||||
|
||||
chunk_id += 1
|
||||
combined_chunks_size += bytes_read
|
||||
|
||||
# In the case we have been given an unknown image size,
|
||||
# set the image_size to the total size of the combined chunks.
|
||||
if image_size == 0:
|
||||
image_size = combined_chunks_size
|
||||
|
||||
# Now we write the object manifest and return the
|
||||
# manifest's etag...
|
||||
manifest = "%s/%s" % (self.container, obj_name)
|
||||
headers = {'ETag': hashlib.md5("").hexdigest(),
|
||||
'X-Object-Manifest': manifest}
|
||||
|
||||
# The ETag returned for the manifest is actually the
|
||||
# MD5 hash of the concatenated checksums of the strings
|
||||
# of each chunk...so we ignore this result in favour of
|
||||
# the MD5 of the entire image file contents, so that
|
||||
# users can verify the image file contents accordingly
|
||||
swift_conn.put_object(self.container, obj_name,
|
||||
None, headers=headers)
|
||||
obj_etag = checksum.hexdigest()
|
||||
|
||||
# NOTE: We return the user and key here! Have to because
|
||||
# location is used by the API server to return the actual
|
||||
# image data. We *really* should consider NOT returning
|
||||
# the location attribute from GET /images/<ID> and
|
||||
# GET /images/details
|
||||
|
||||
return (location.get_uri(), image_size, obj_etag)
|
||||
except swift_client.ClientException, e:
|
||||
if e.http_status == httplib.CONFLICT:
|
||||
raise exception.Duplicate(_("Swift already has an image at "
|
||||
"location %s") % location.get_uri())
|
||||
msg = (_("Failed to add object to Swift.\n"
|
||||
"Got error from Swift: %(e)s") % locals())
|
||||
logger.error(msg)
|
||||
raise glance.store.BackendException(msg)
|
||||
|
||||
def delete(self, location):
|
||||
"""
|
||||
Takes a `glance.store.location.Location` object that indicates
|
||||
where to find the image file to delete
|
||||
|
||||
:location `glance.store.location.Location` object, supplied
|
||||
from glance.store.location.get_location_from_uri()
|
||||
|
||||
:raises NotFound if image does not exist
|
||||
"""
|
||||
loc = location.store_location
|
||||
swift_conn = self._make_swift_connection(
|
||||
auth_url=loc.swift_auth_url, user=loc.user, key=loc.key)
|
||||
|
||||
try:
|
||||
# We request the manifest for the object. If one exists,
|
||||
# that means the object was uploaded in chunks/segments,
|
||||
# and we need to delete all the chunks as well as the
|
||||
# manifest.
|
||||
manifest = None
|
||||
try:
|
||||
headers = swift_conn.head_object(loc.container, loc.obj)
|
||||
manifest = headers.get('x-object-manifest')
|
||||
except swift_client.ClientException, e:
|
||||
if e.http_status != httplib.NOT_FOUND:
|
||||
raise
|
||||
if manifest:
|
||||
# Delete all the chunks before the object manifest itself
|
||||
obj_container, obj_prefix = manifest.split('/', 1)
|
||||
for segment in swift_conn.get_container(obj_container,
|
||||
prefix=obj_prefix)[1]:
|
||||
# TODO(jaypipes): This would be an easy area to parallelize
|
||||
# since we're simply sending off parallelizable requests
|
||||
# to Swift to delete stuff. It's not like we're going to
|
||||
# be hogging up network or file I/O here...
|
||||
swift_conn.delete_object(obj_container, segment['name'])
|
||||
|
||||
else:
|
||||
swift_conn.delete_object(loc.container, loc.obj)
|
||||
|
||||
except swift_client.ClientException, e:
|
||||
if e.http_status == httplib.NOT_FOUND:
|
||||
uri = location.get_store_uri()
|
||||
raise exception.NotFound(_("Swift could not find image at "
|
||||
"uri %(uri)s") % locals())
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class ChunkReader(object):
|
||||
def __init__(self, fd, checksum, total):
|
||||
self.fd = fd
|
||||
self.checksum = checksum
|
||||
self.total = total
|
||||
self.bytes_read = 0
|
||||
|
||||
def read(self, i):
|
||||
left = self.total - self.bytes_read
|
||||
if i > left:
|
||||
i = left
|
||||
result = self.fd.read(i)
|
||||
self.bytes_read += len(result)
|
||||
self.checksum.update(result)
|
||||
return result
|
||||
|
||||
|
||||
def create_container_if_missing(container, swift_conn, conf):
|
||||
"""
|
||||
Creates a missing container in Swift if the
|
||||
``swift_store_create_container_on_put`` option is set.
|
||||
|
||||
:param container: Name of container to create
|
||||
:param swift_conn: Connection to Swift
|
||||
:param conf: Option mapping
|
||||
"""
|
||||
try:
|
||||
swift_conn.head_container(container)
|
||||
except swift_client.ClientException, e:
|
||||
if e.http_status == httplib.NOT_FOUND:
|
||||
if conf.swift_store_create_container_on_put:
|
||||
try:
|
||||
swift_conn.put_container(container)
|
||||
except swift_client.ClientException, e:
|
||||
msg = _("Failed to add container to Swift.\n"
|
||||
"Got error from Swift: %(e)s") % locals()
|
||||
raise glance.store.BackendException(msg)
|
||||
else:
|
||||
msg = (_("The container %(container)s does not exist in "
|
||||
"Swift. Please set the "
|
||||
"swift_store_create_container_on_put option"
|
||||
"to add container to Swift automatically.")
|
||||
% locals())
|
||||
raise glance.store.BackendException(msg)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
glance.store.register_store(__name__, ['swift', 'swift+http', 'swift+https'])
|
||||
Reference in New Issue
Block a user