From b0eb5889757d2dacca4c8529ad7cf4df9d548aca Mon Sep 17 00:00:00 2001 From: mattray Date: Sat, 23 Jun 2012 00:06:54 -0500 Subject: [PATCH] osops-utils cookbook from https://github.com/rcbops-cookbooks/osops-utils, waiting for permission to push to community site, will remove once upstream version is published --- cookbooks/osops-utils/.gitignore | 1 + cookbooks/osops-utils/README.md | 62 ++++ cookbooks/osops-utils/libraries/database.rb | 36 ++ .../osops-utils/libraries/ip_location.rb | 327 ++++++++++++++++++ cookbooks/osops-utils/metadata.rb | 6 + cookbooks/osops-utils/recipes/default.rb | 18 + 6 files changed, 450 insertions(+) create mode 100644 cookbooks/osops-utils/.gitignore create mode 100644 cookbooks/osops-utils/README.md create mode 100644 cookbooks/osops-utils/libraries/database.rb create mode 100644 cookbooks/osops-utils/libraries/ip_location.rb create mode 100644 cookbooks/osops-utils/metadata.rb create mode 100644 cookbooks/osops-utils/recipes/default.rb diff --git a/cookbooks/osops-utils/.gitignore b/cookbooks/osops-utils/.gitignore new file mode 100644 index 0000000..54e2457 --- /dev/null +++ b/cookbooks/osops-utils/.gitignore @@ -0,0 +1 @@ +metadata.json diff --git a/cookbooks/osops-utils/README.md b/cookbooks/osops-utils/README.md new file mode 100644 index 0000000..4eb271b --- /dev/null +++ b/cookbooks/osops-utils/README.md @@ -0,0 +1,62 @@ +Description +=========== + +Miscellaneous library functions for OpenStack. This currently includes: + + * ip address location + +Requirements +============ + +Uses the Ruby libraries `chef/search/query`, `ipaddr` and `uri` + +Attributes +========== + +`osops_networks` is a list of network names and associated CIDR. These are used in the `get_ip` functions. + +Usage +===== + +node['osops_networks']['localnet'] = 127.0.0.0/8 + +node['osops_networks']['management'] = 10.0.1.0/24 + +ip = get_ip_for_net("localnet") # returns 127.0.0.1 + +ip = get_ip_for_net("management") # returns the address on management, or error + +License and Author +================== + +Author:: Justin Shepherd () + +Author:: Jason Cannavale () + +Author:: Ron Pedde () + +Author:: Joseph Breu () + +Author:: William Kelly () + +Author:: Darren Birkett () + +Author:: Evan Callicoat () + +Author:: Matt Ray () + +Copyright 2012 Rackspace, 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. diff --git a/cookbooks/osops-utils/libraries/database.rb b/cookbooks/osops-utils/libraries/database.rb new file mode 100644 index 0000000..7f863f9 --- /dev/null +++ b/cookbooks/osops-utils/libraries/database.rb @@ -0,0 +1,36 @@ +module RCB + def create_db_and_user(db, db_name, username, pw) + db_info = nil + case db + when "mysql" + mysql_info = get_settings_by_role("mysql-master", "mysql") + connection_info = {:host => mysql_info["bind_address"], :username => "root", :password => mysql_info["server_root_password"]} + + # create database + mysql_database "create #{db_name} database" do + connection connection_info + database_name db_name + action :create + end + + # create user + mysql_database_user username do + connection connection_info + password pw + action :create + end + + # grant privs to user + mysql_database_user username do + connection connection_info + password pw + database_name db_name + host '%' + privileges [:all] + action :grant + end + db_info = mysql_info + end + db_info + end +end diff --git a/cookbooks/osops-utils/libraries/ip_location.rb b/cookbooks/osops-utils/libraries/ip_location.rb new file mode 100644 index 0000000..ed1c0a1 --- /dev/null +++ b/cookbooks/osops-utils/libraries/ip_location.rb @@ -0,0 +1,327 @@ +#!/usr/bin/env ruby + +require "chef/search/query" +require "ipaddr" +require "uri" + +module RCB + # These are the new endpoint functions, that return much more information about + # endpoints. Sufficient to configure something, I hope. :/ + + # Get the bind information necessary for a service. + # ex: IPManagement.get_bind_endpoint("keystone","admin") + # { "host" => "10.1.0.2", + # "port" => 35357, + # "scheme" => "http", + # "path" => "/v2.0", + # "uri" => "http://10.1.0.2:35357/v2.0" + # "network" => "management" + # + # To define this resource, there must be a service entries like... + # node["keystone"]["services"]["admin"] = + # { "network" => "management", "port" => 35357 } + # + # IP address is derived from required network, unless overridden + # Protocol defaults to "http", path defaults to "/". If a URI + # is specified, it overrides all other settings, otherwise it is + # composed from the individual components + + def rcb_exit_error(msg) + Chef::Log.error(msg) + raise msg + end + + def rcb_safe_deref(hash, path) + current = hash + + Chef::Log.debug("Searching for #{path} in #{hash}") + path_ary = path.split(".") + path_ary.each do |k| + if current and current.has_key?(k) + current = current[k] + else + current = nil + end + end + + current + end + + # stub until we can migrate out the IPManagement stuff + def get_ip_for_net(network, nodeish = nil) + _, ip = get_if_ip_for_net(network, nodeish) + return ip + end + + def get_if_for_net(network, nodeish = nil) + iface, _ = get_if_ip_for_net(network, nodeish) + return iface + end + + def get_if_ip_for_net(network, nodeish = nil) + nodish = node unless nodeish + + if network == "all" + return "0.0.0.0" + end + + if network == "localhost" + return "127.0.0.1" + end + + if not (node.has_key?("osops_networks") and node["osops_networks"].has_key?(network)) then + error = "Can't find network #{network}" + Chef::Log.error(error) + raise error + end + + net = IPAddr.new(node["osops_networks"][network]) + node["network"]["interfaces"].each do |interface| + interface[1]["addresses"].each do |k,v| + if v["family"] == "inet6" or v["family"] == "inet" then + addr=IPAddr.new(k) + if net.include?(addr) then + return [interface[0], k] + end + end + end + end + + error = "Can't find address on network #{network} for node" + Chef::Log.error(error) + raise error + end + + def get_bind_endpoint(server, service, nodeish=nil) + retval = {} + nodeish = node unless nodeish + + if svc = rcb_safe_deref(nodeish, "#{server}.services.#{service}") + retval["path"] = svc["path"] || "/" + retval["scheme"] = svc["scheme"] || "http" + retval["port"] = svc["port"] || "80" + + # if we have an endpoint, we'll just parse the pieces + if svc.has_key?("uri") + uri = URI(svc["uri"]) + ["path", "scheme", "port", "host"].each do |x| + retval.merge(x => uri.send(x)) + end + elsif svc.has_key?("host") + retval["host"] = svc["host"] + retval["uri"] = "#{retval['scheme']}://#{retval['host']}:#{retval['port']}" + retval["uri"] += retval["path"] + else + # we'll get the network from the osops network + retval["host"] = Chef::Recipe::IPManagement.get_ip_for_net(svc["network"], nodeish) + retval["uri"] = "#{retval['scheme']}://#{retval['host']}:#{retval['port']}" + retval["uri"] += retval["path"] + end + + retval + else + Chef::Log.warn("Cannot find server/service #{server}/#{service}") + nil + end + end + + # Get the access endpoint for a role. + # + # If a role search returns no results, but the role is in our + # current runlist, use the bind endpoint from the local node + # attributes. + # + # If a role search returns more than one result, then return + # the LB config for that service + # + # If the role search returns exactly one result, then use + # the bind endpoint for the service according to that nodes attributes + # + + def get_access_endpoint(role, server, service) + query = "roles:#{role} AND chef_environment:#{node.chef_environment}" + result, _, _ = Chef::Search::Query.new.search(:node, query) + + if result.length == 1 and result[0].name == node.name + Chef::Log.debug("Found 1 result for #{role}/#{server}/#{service}, and it's me!") + result = [node] + elsif result.length == 0 and node["roles"].include?(role) + Chef::Log.debug("Found 0 result for #{role}/#{server}/#{service}, but I'm a role-holder!") + result = [node] + end + + if result.length == 0 + Chef::Log.warn("Cannot find #{server}/#{service} for role #{role}") + nil + elsif result.length > 1 + get_lb_endpoint(server,service) + else + get_bind_endpoint(server, service, result[0]) + end + end + + # return the endpoint info for all roles matching the + # the service. This differs from access_endpoint, as it + # returns all the candidates, not merely the LB vip + # + def get_realserver_endpoints(role, server, service) + query = "roles:#{role} AND chef_environment:#{node.chef_environment}" + result, _, _ = Chef::Search::Query.new.search(:node, query) + + # if no query results, but role is in current runlist, use that + result = [ node ] if result.length == 0 and node["roles"].include?(role) + + result.map { |nodeish| get_bind_endpoint(server, service, nodeish) } + end + + # Get a specific node hash from another node by role + # + # In the event of a search with multiple results, + # it returns the first match + # + # In the event of a search with a no matches, if the role + # is held on the running node, then the current node hash + # values will be returned + # + def get_settings_by_role(role, settings) + if node["roles"].include?(role) + node[settings] + else + query = "roles:#{role} AND chef_environment:#{node.chef_environment}" + result, _, _ = Chef::Search::Query.new.search(:node, query) + + if result.length == 0 + nil + else + result[0][settings] + end + end + end + + # Get a specific node hash from another node by recipe + # + # In the event of a search with multiple results, + # it returns the first match + # + # In the event of a search with a no matches, if the role + # is held on the running node, then the current node hash + # values will be returned + # + def get_settings_by_recipe(recipe, settings) + if node["recipes"].include?(recipe) + node[settings] + else + query = "recipes:#{recipe} AND chef_environment:#{node.chef_environment}" + result, _, _ = Chef::Search::Query.new.search(:node, query) + + if result.length == 0 + Chef::Log.warn("Can't find node with recipe #{recipe}") + nil + else + result[0][settings] + end + end + end + + def get_lb_endpoint(server, service) + rcb_exit_error("LB endpoints not yet defined") + end +end + +class Chef::Recipe + include RCB +end + +class Chef::Recipe::IPManagement + # find the local ip for a host on a specific network + def self.get_ip_for_net(network, node) + if network == "all" + return "0.0.0.0" + end + + if network == "localhost" + return "127.0.0.1" + end + + # remap the network if a map is present + if node.has_key?("osops_networks") and + node["osops_networks"].has_key?("mapping") and + node["osops_networks"]["mapping"].has_key?(network) + network = node["osops_networks"]["mapping"][network] + end + + if not (node.has_key?("osops_networks") and node["osops_networks"].has_key?(network)) then + error = "Can't find network #{network}" + Chef::Log.error(error) + raise error + end + + net = IPAddr.new(node["osops_networks"][network]) + node["network"]["interfaces"].each do |interface| + interface[1]["addresses"].each do |k,v| + if v["family"] == "inet6" or v["family"] == "inet" then + addr=IPAddr.new(k) + if net.include?(addr) then + return k + end + end + end + end + + error = "Can't find address on network #{network} for node" + Chef::Log.error(error) + raise error + end + + # find the realserver ips for a particular role + def self.get_ips_for_role(role, network, node) + if Chef::Config[:solo] then + return [self.get_ip_for_net(network, node)] + else + candidates, _, _ = Chef::Search::Query.new.search(:node, "chef_environment:#{node.chef_environment} AND roles:#{role}") + if candidates == nil or candidates.length <= 0 + if node["roles"].include?(role) + candidates = [node] + end + end + + if candidates == nil or candidates.length <= 0 + error = "Can't find any candidates for role #{role} in environment #{node.chef_environment}" + Chef::Log.error(error) + raise error + end + + return candidates.map { |x| get_ip_for_net(network, x) } + end + end + + # find the loadbalancer ip for a particular role + def self.get_access_ip_for_role(role, network, node) + if Chef::Config[:solo] then + return self.get_ip_for_net(network, node) + else + candidates, _, _ = Chef::Search::Query.new.search(:node, "chef_environment:#{node.chef_environment} AND roles:#{role}") + if candidates == nil or candidates.length == 0 + if node["roles"].include?(role) + candidates = [ node ] + end + end + + if candidates.length == 1 then + return get_ip_for_net(network, candidates[0]) + elsif candidates.length == 0 then + error = "Can't find any candidates for role #{role} in environment #{node.chef_environment}" + Chef::Log.error(error) + raise error + else + if not node["osops_networks"] or not node["osops_networks"]["vips"] or not node["osops_networks"]["vips"][role] then + error = "Can't find lb vip for #{role} (osops_networks/vips/#{role}) in environment, with #{candidates.length} #{role} nodes" + Chef::Log.error(error) + raise error + else + return node["osops_networks"]["vips"][role] + end + end + end + end +end diff --git a/cookbooks/osops-utils/metadata.rb b/cookbooks/osops-utils/metadata.rb new file mode 100644 index 0000000..5ef12f4 --- /dev/null +++ b/cookbooks/osops-utils/metadata.rb @@ -0,0 +1,6 @@ +maintainer "Rackspace Hosting" +maintainer_email "osops@lists.launchpad.net" +license "Apache 2.0" +description "Installs/Configures osops-utils" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "1.0.2" diff --git a/cookbooks/osops-utils/recipes/default.rb b/cookbooks/osops-utils/recipes/default.rb new file mode 100644 index 0000000..2607a73 --- /dev/null +++ b/cookbooks/osops-utils/recipes/default.rb @@ -0,0 +1,18 @@ +# +# Cookbook Name:: osops-utils +# Recipe:: default +# +# Copyright 2012, Rackspace Hosting +# +# 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. +#