From bee8517bf4f70c49e010b5e1c4938cb59fdc69b1 Mon Sep 17 00:00:00 2001 From: Matthew Mosesohn Date: Thu, 21 Apr 2016 19:50:43 +0300 Subject: [PATCH] Add nova_service type for purging nova services from offline hosts Offline/deleted hosts show as offline forever in the service list. This undesirable output can be mitigated by offering a provider that allows a user to mark such nova services as 'absent'. Includes release note. Change-Id: I303ab218bc6a48cf2c60727feecc522040a80a68 Related-Bug: #1471172 --- lib/puppet/provider/nova_service/openstack.rb | 66 +++++++++++++++++ lib/puppet/type/nova_service.rb | 72 +++++++++++++++++++ ...ova_service_provider-f9c1dc94c575692c.yaml | 7 ++ spec/type/nova_service_spec.rb | 29 ++++++++ .../provider/nova_service/openstack_spec.rb | 67 +++++++++++++++++ spec/unit/type/nova_service_spec.rb | 24 +++++++ 6 files changed, 265 insertions(+) create mode 100644 lib/puppet/provider/nova_service/openstack.rb create mode 100644 lib/puppet/type/nova_service.rb create mode 100644 releasenotes/notes/nova_service_provider-f9c1dc94c575692c.yaml create mode 100644 spec/type/nova_service_spec.rb create mode 100644 spec/unit/provider/nova_service/openstack_spec.rb create mode 100644 spec/unit/type/nova_service_spec.rb diff --git a/lib/puppet/provider/nova_service/openstack.rb b/lib/puppet/provider/nova_service/openstack.rb new file mode 100644 index 000000000..93f9cba91 --- /dev/null +++ b/lib/puppet/provider/nova_service/openstack.rb @@ -0,0 +1,66 @@ +require 'puppet/provider/nova' + +Puppet::Type.type(:nova_service).provide( + :openstack, + :parent => Puppet::Provider::Nova +) do + desc <<-EOT + Provider to manage nova host services + EOT + + @credentials = Puppet::Provider::Openstack::CredentialsV3.new + + mk_resource_methods + + def self.instances + hosts = {} + request('compute service', 'list').collect do |host_svc| + hname = host_svc[:host] + if hosts[hname].nil? + hosts[hname] = Hash.new {|h,k| h[k]=[]} + hosts[hname][:ids] = [] + hosts[hname][:service_name] = [] + end + hosts[hname][:ids] << host_svc[:id] + hosts[hname][:service_name] << host_svc[:binary] + end + hosts.collect do |hname, host| + new( + :ensure => :present, + :name => hname, + :ids => host[:ids], + :service_name => host[:service_name] + ) + end + end + + def self.prefetch(resources) + instances_ = self.instances + resources.keys.each do |name| + if provider = instances_.find{ |instance| instance.name == name } + resources[name].provider = provider + end + end + end + + def exists? + @property_hash[:ensure] == :present + end + + def destroy + return unless @property_hash[:ids].kind_of?(Array) + svcname_id_map = @property_hash[:service_name].zip(@property_hash[:ids]) || {} + svcname_id_map.each do |service_name, id| + if (@resource[:service_name] == '' || + (@resource[:service_name] == service_name)) + self.class.request('compute service', 'delete', id) + end + end + @property_hash[:ensure] = :absent + end + + def create + warning("Nova_service provider can only delete compute services because "\ + "of openstackclient limitations.") + end +end diff --git a/lib/puppet/type/nova_service.rb b/lib/puppet/type/nova_service.rb new file mode 100644 index 000000000..ecb9d5457 --- /dev/null +++ b/lib/puppet/type/nova_service.rb @@ -0,0 +1,72 @@ +# Copyright (C) 2016 Mirantis Inc. +# +# Author: Matthew Mosesohn +# +# 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. +# +# nova_security_group type +# +# == Parameters +# [*name*] +# Name for the host +# Required +# +# [*ensure*] +# Marks status of service(s) on a given host. +# Possible values are enabled, disabled, purged +# Optional +# +# [*service_name*] +# Name of a given service. Defaults to "undef", which modifies all. +# Optional +# + + +require 'puppet' + +Puppet::Type.newtype(:nova_service) do + + @doc = "Manage status of nova services on hosts." + + ensurable + + newparam(:name, :namevar => true) do + desc 'Name of host' + validate do |value| + if not value.is_a? String + raise ArgumentError, "name parameter must be a String" + end + unless value =~ /^[a-zA-Z0-9\-_.]+$/ + raise ArgumentError, "#{value} is not a valid name" + end + end + end + + newproperty(:ids) do + desc 'The unique Ids of the compute service' + validate do |v| + raise ArgumentError, 'This is a read only property' + end + end + + newproperty(:service_name, :array_matching => :all) do + desc "String or Array of services on a host to modify" + validate do |value| + if not value.is_a? String and not value.is_a? Array + raise ArgumentError, "service_name parameter must be String or Array" + end + end + defaultto '' + end + +end diff --git a/releasenotes/notes/nova_service_provider-f9c1dc94c575692c.yaml b/releasenotes/notes/nova_service_provider-f9c1dc94c575692c.yaml new file mode 100644 index 000000000..da1740496 --- /dev/null +++ b/releasenotes/notes/nova_service_provider-f9c1dc94c575692c.yaml @@ -0,0 +1,7 @@ +--- +features: + - Nova service management. + Ability to remove services left behind by Nova after + disabling or decommissioning a host. Provider can + delete all (default) or an array of specified + service names. diff --git a/spec/type/nova_service_spec.rb b/spec/type/nova_service_spec.rb new file mode 100644 index 000000000..e69d18b1d --- /dev/null +++ b/spec/type/nova_service_spec.rb @@ -0,0 +1,29 @@ +# run with: rspec spec/type/nova_service_spec.rb + +require 'spec_helper' + + +describe Puppet::Type.type(:nova_service) do + before :each do + @provider_class = described_class.provide(:simple) do + mk_resource_methods + def create; end + def delete; end + def exists?; get(:ensure) != :absent; end + def flush; end + def self.instances; []; end + end + end + + it "should be able to create an instance" do + expect(described_class.new(:name => 'nova1')).not_to be_nil + end + + it "should return the given values" do + c = described_class.new(:name => 'nova1', + :service_name => 'nova-scheduler') + expect(c[:name]).to eq("nova1") + expect(c[:service_name]).to eq('nova-scheduler') + end + +end diff --git a/spec/unit/provider/nova_service/openstack_spec.rb b/spec/unit/provider/nova_service/openstack_spec.rb new file mode 100644 index 000000000..c99dba2d5 --- /dev/null +++ b/spec/unit/provider/nova_service/openstack_spec.rb @@ -0,0 +1,67 @@ +require 'puppet' +require 'spec_helper' +require 'puppet/provider/nova_service/openstack' + +provider_class = Puppet::Type.type(:nova_service).provider(:openstack) + +describe provider_class do + + shared_examples 'authenticated with environment variables' do + ENV['OS_USERNAME'] = 'test' + ENV['OS_PASSWORD'] = 'abc123' + ENV['OS_PROJECT_NAME'] = 'test' + ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v3' + end + + describe 'managing nova services' do + + let(:service_attrs) do + { + :name => 'myhost', + :ensure => 'present', + :service_name => ['waffles'], + #:ids => ['1'] + } + end + + let(:service_attrs_without_name) do + { + :name => 'myhost', + :ensure => 'absent', + } + end + + let(:resource) do + Puppet::Type::Nova_service.new(service_attrs) + end + + let(:provider) do + provider_class.new(resource) + end + + it_behaves_like 'authenticated with environment variables' do + describe '#instances' do + it 'finds existing services' do + provider_class.expects(:openstack) + .with('compute service', 'list', '--quiet', '--format', 'csv', []) + .returns('"Id","Binary","Host","Zone","Status","State","Updated At" +"1","waffles","myhost","internal","enabled","down","2016-01-01T12:00:00.000000"') + + instances = provider_class.instances + expect(instances.count).to eq(1) + end + end + + describe '#destroy' do + + it 'destroys a service' do + provider.class.stubs(:openstack) + .with('compute service', 'delete', []) + provider.destroy + expect(provider.exists?).to be_falsey + end + end + + end + end +end diff --git a/spec/unit/type/nova_service_spec.rb b/spec/unit/type/nova_service_spec.rb new file mode 100644 index 000000000..f0cb3d0db --- /dev/null +++ b/spec/unit/type/nova_service_spec.rb @@ -0,0 +1,24 @@ +require 'puppet' +require 'puppet/type/nova_service' + +describe Puppet::Type.type(:nova_service) do + + before :each do + Puppet::Type.rmtype(:nova_service) + end + + it 'should raise error for setting ids property' do + incorrect_input = { + :name => 'test_type', + :ids => 'some_id' + } + expect { Puppet::Type.type(:nova_service).new(incorrect_input) }.to raise_error(Puppet::ResourceError, /This is a read only property/) + end + + it 'should raise error if wrong format of name' do + incorrect_input = { + :name => ['node-1','node-2'], + } + expect { Puppet::Type.type(:nova_service).new(incorrect_input) }.to raise_error(Puppet::ResourceError, /name parameter must be a String/) + end +end