diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb index 2c1cffe38..04c807383 100644 --- a/lib/puppet/type/zone.rb +++ b/lib/puppet/type/zone.rb @@ -1,374 +1,375 @@ require 'puppet/property/list' Puppet::Type.newtype(:zone) do @doc = "Manages Solaris zones. **Autorequires:** If Puppet is managing the directory specified as the root of the zone's filesystem (with the `path` attribute), the zone resource will autorequire that directory." class StateMachine # A silly little state machine. def initialize @state = {} @sequence = [] @state_aliases = {} @default = nil end # The order of calling insert_state is important def insert_state(name, transitions) @sequence << name @state[name] = transitions end def alias_state(state, salias) @state_aliases[state] = salias end def name(n) @state_aliases[n.to_sym] || n.to_sym end def index(state) @sequence.index(name(state)) end # return all states between fs and ss excluding fs def sequence(fs, ss) fi = index(fs) si= index(ss) (if fi > si then @sequence[si .. fi].map{|i| @state[i]}.reverse else @sequence[fi .. si].map{|i| @state[i]} end)[1..-1] end def cmp?(a,b) index(a) < index(b) end end ensurable do desc "The running state of the zone. The valid states directly reflect the states that `zoneadm` provides. The states are linear, in that a zone must be `configured`, then `installed`, and only then can be `running`. Note also that `halt` is currently used to stop zones." def self.fsm return @fsm if @fsm @fsm = StateMachine.new end def self.alias_state(values) values.each do |k,v| fsm.alias_state(k,v) end end def self.seqvalue(name, hash) fsm.insert_state(name, hash) self.newvalue name end # This is seq value because the order of declaration is important. # i.e we go linearly from :absent -> :configured -> :installed -> :running seqvalue :absent, :down => :destroy seqvalue :configured, :up => :configure, :down => :uninstall seqvalue :installed, :up => :install, :down => :stop seqvalue :running, :up => :start alias_state :incomplete => :installed, :ready => :installed, :shutting_down => :running defaultto :running def self.state_sequence(first, second) fsm.sequence(first, second) end # Why override it? because property/ensure.rb has a default retrieve method # that knows only about :present and :absent. That method just calls # provider.exists? and returns :present if a result was returned. def retrieve provider.properties[:ensure] end def provider_sync_send(method) warned = false while provider.processing? next if warned info "Waiting for zone to finish processing" warned = true sleep 1 end provider.send(method) + provider.flush() end def sync method = nil direction = up? ? :up : :down # We need to get the state we're currently in and just call # everything between it and us. self.class.state_sequence(self.retrieve, self.should).each do |state| method = state[direction] raise Puppet::DevError, "Cannot move #{direction} from #{st[:name]}" unless method provider_sync_send(method) end ("zone_#{self.should}").intern end # Are we moving up the property tree? def up? self.class.fsm.cmp?(self.retrieve, self.should) end end newparam(:name) do desc "The name of the zone." isnamevar end newparam(:id) do desc "The numerical ID of the zone. This number is autogenerated and cannot be changed." end newparam(:clone) do desc "Instead of installing the zone, clone it from another zone. If the zone root resides on a zfs file system, a snapshot will be used to create the clone; if it resides on a ufs filesystem, a copy of the zone will be used. The zone from which you clone must not be running." end newproperty(:ip, :parent => Puppet::Property::List) do require 'ipaddr' desc "The IP address of the zone. IP addresses must be specified with the interface, separated by a colon, e.g.: bge0:192.168.0.1. For multiple interfaces, specify them in an array." # The default action of list should is to lst.join(' '). By specifying # @should, we ensure the should remains an array. If we override should, we # should also override insync?() -- property/list.rb def should @should end # overridden so that we match with self.should def insync?(is) return true unless is is = [] if is == :absent is.sort == self.should.sort end end newproperty(:iptype) do desc "The IP stack type of the zone." defaultto :shared newvalue :shared newvalue :exclusive end newproperty(:autoboot, :boolean => true) do desc "Whether the zone should automatically boot." defaultto true newvalues(:true, :false) end newproperty(:path) do desc "The root of the zone's filesystem. Must be a fully qualified file name. If you include `%s` in the path, then it will be replaced with the zone's name. Currently, you cannot use Puppet to move a zone. Consequently this is a readonly property." validate do |value| raise ArgumentError, "The zone base must be fully qualified" unless value =~ /^\// end munge do |value| if value =~ /%s/ value % @resource[:name] else value end end end newproperty(:pool) do desc "The resource pool for this zone." end newproperty(:shares) do desc "Number of FSS CPU shares allocated to the zone." end newproperty(:dataset, :parent => Puppet::Property::List ) do desc "The list of datasets delegated to the non-global zone from the global zone. All datasets must be zfs filesystem names which are different from the mountpoint." def should @should end # overridden so that we match with self.should def insync?(is) return true unless is is = [] if is == :absent is.sort == self.should.sort end validate do |value| unless value !~ /^\// raise ArgumentError, "Datasets must be the name of a zfs filesystem" end end end newproperty(:inherit, :parent => Puppet::Property::List) do desc "The list of directories that the zone inherits from the global zone. All directories must be fully qualified." def should @should end # overridden so that we match with self.should def insync?(is) return true unless is is = [] if is == :absent is.sort == self.should.sort end validate do |value| unless value =~ /^\// raise ArgumentError, "Inherited filesystems must be fully qualified" end end end # Specify the sysidcfg file. This is pretty hackish, because it's # only used to boot the zone the very first time. newparam(:sysidcfg) do desc %{The text to go into the `sysidcfg` file when the zone is first booted. The best way is to use a template: # $confdir/modules/site/templates/sysidcfg.erb system_locale=en_US timezone=GMT terminal=xterms security_policy=NONE root_password=<%= password %> timeserver=localhost name_service=DNS {domain_name=<%= domain %> name_server=<%= nameserver %>} network_interface=primary {hostname=<%= realhostname %> ip_address=<%= ip %> netmask=<%= netmask %> protocol_ipv6=no default_route=<%= defaultroute %>} nfs4_domain=dynamic And then call that: zone { myzone: ip => "bge0:192.168.0.23", sysidcfg => template("site/sysidcfg.erb"), path => "/opt/zones/myzone", realhostname => "fully.qualified.domain.name" } The `sysidcfg` only matters on the first booting of the zone, so Puppet only checks for it at that time.} end newparam(:create_args) do desc "Arguments to the `zonecfg` create command. This can be used to create branded zones." end newparam(:install_args) do desc "Arguments to the `zoneadm` install command. This can be used to create branded zones." end newparam(:realhostname) do desc "The actual hostname of the zone." end # If Puppet is also managing the base dir or its parent dir, list them # both as prerequisites. autorequire(:file) do if @parameters.include? :path [@parameters[:path].value, ::File.dirname(@parameters[:path].value)] else nil end end # If Puppet is also managing the zfs filesystem which is the zone dataset # then list it as a prerequisite. Zpool's get autorequired by the zfs # type. We just need to autorequire the dataset zfs itself as the zfs type # will autorequire all of the zfs parents and zpool. autorequire(:zfs) do # Check if we have datasets in our zone configuration and autorequire each dataset self[:dataset] if @parameters.include? :dataset end def validate_ip(ip, name) IPAddr.new(ip) if ip rescue ArgumentError self.fail "'#{ip}' is an invalid #{name}" end def validate_exclusive(interface, address, router) return if !interface.nil? and address.nil? self.fail "only interface may be specified when using exclusive IP stack: #{interface}:#{address}" end def validate_shared(interface, address, router) self.fail "ip must contain interface name and ip address separated by a \":\"" if interface.nil? or address.nil? [address, router].each do |ip| validate_ip(address, "IP address") unless ip.nil? end end validate do return unless self[:ip] # self[:ip] reflects the type passed from proeprty:ip.should. If we # override it and pass @should, then we get an array here back. self[:ip].each do |ip| interface, address, router = ip.split(':') if self[:iptype] == :shared validate_shared(interface, address, router) else validate_exclusive(interface, address, router) end end end def retrieve provider.flush hash = provider.properties return setstatus(hash) unless hash.nil? or hash[:ensure] == :absent # Return all properties as absent. return Hash[properties.map{|p| [p, :absent]} ] end # Take the results of a listing and set everything appropriately. def setstatus(hash) prophash = {} hash.each do |param, value| next if param == :name case self.class.attrtype(param) when :property # Only try to provide values for the properties we're managing prop = self.property(param) prophash[prop] = value if prop else self[param] = value end end prophash end end