Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge "[services-ng] avoid migration on every start of postgresql node"

  • Loading branch information...
commit 22b8b6bec457a0100820b82d213b71708f5682ed 2 parents 0fcb3ad + 68bebbf
@kushmerick kushmerick authored Gerrit Code Review committed
View
167 ng/postgresql/lib/postgresql_service/without_warden.rb
@@ -39,177 +39,10 @@ def pre_send_announcement_prepare
def pre_send_announcement_internal(options)
pgProvisionedService.all.each do |provisionedservice|
setup_global_connection provisionedservice
- migrate_instance provisionedservice
@capacity -= capacity_unit
end
end
-
- # This method performs whatever 'migration' (upgrade/downgrade)
- # steps are required due to incompatible code changes. There is no
- # concept of an instance's "version", so migration code may need to
- # inspect the instance to determine what migrations are required.
- def migrate_instance(provisionedservice)
- # Services-r7 and earlier had a bug whereby database objects were
- # owned by the users created by bind operations, which caused
- # various problems (eg these objects were discarded on an 'unbind'
- # operation, only the original creator of an object could modify
- # it, etc). Services-r8 fixes this problem by granting all 'children'
- # bind users to a 'parent' role, and setting all 'children' bind users'
- # default connection session to be 'parent' role's configuration parameter.
- # But this fix only works for newly created users and objects, so we
- # need to call this object-ownership method to migration 'old' users
- # and objects. we don't need to worry about calling it more than once
- # because doing so is harmless.
- manage_object_ownership(provisionedservice.name)
- # Services-r11 and earlier the user could not have temp privilege to
- # create temporary tables/views/sequences. Services-r12 solves this issue.
- manage_temp_privilege(provisionedservice.name)
- # In earlier releases, users should not have create privilege to create schmea in databases.
- manage_create_privilege(provisionedservice.name)
- # Fix the bug: when restoring database, the max connection limit is set to -1
- # https://www.pivotaltracker.com/story/show/34260725
- manage_maxconnlimit(provisionedservice.name)
- end
-
- def get_expected_children(name)
- # children according to pgProvisionedService
- children = pgProvisionedService.get(name).pgbindusers.all(:default_user => false)
- children = children.map { |child| child.user } + children.map { |child| child.sys_user }
- children
- end
-
- def get_actual_children(connection, name, parent)
- instance = pgProvisionedService.get(name)
- raise "Can't find instance #{name}" unless instance
- version = instance.version
-
- # children according to postgres itself
- children = []
- rows = connection.query("SELECT datacl FROM pg_database WHERE datname='#{name}'")
- raise "Can't get datacl" if rows.nil? || rows.num_tuples < 1
- datacl = rows[0]['datacl']
- # a typical pg_database.datacl value:
- # {vcap=CTc/vcap,suf4f57864f519412b82ffd0b75d02dcd1=c/vcap,u2e47852f15544536b2f69c0f72052847=c/vcap,su76f8095858e742d1954544c722b277f8=c/vcap,u02b45d2974644895b1b03a92749250b2=c/vcap,su7950e259bbe946328ba4e3540c141f4b=c/vcap,uaf8982bc76324c6e9a09596fa1e57fc3=c/vcap}
- raise "Datacl is nil/deformed" if datacl.nil? || datacl.length < 2
- nonchildren = [@postgresql_configs[version]["user"], parent.user, parent.sys_user, '']
- datacl[1,datacl.length-1].split(',').each do |aclitem|
- child = aclitem.split('=')[0]
- children << child unless nonchildren.include?(child)
- end
- children
- end
-
- def get_ruly_children(connection, parent)
- query = <<-end_of_query
- SELECT rolname
- FROM pg_roles
- WHERE oid IN (
- SELECT member
- FROM pg_auth_members
- WHERE roleid IN (
- SELECT oid
- FROM pg_roles
- WHERE rolname='#{parent.user}'
- )
- );
- end_of_query
- ruly_children = connection.query(query).map { |row| row['rolname'] }
- ruly_children
- end
-
- def get_unruly_children(connection, parent, children)
- # children which are not in fact children of the parent. (we don't
- # handle children that somehow have the *wrong* parent, but that
- # won't happen :-)
- children - get_ruly_children(connection, parent)
- end
-
- def manage_object_ownership(name)
- # figure out which children *should* exist
- expected_children = get_expected_children name
- # optimization: the set of children we need to take action for is
- # a subset of the expected childen, so if there are no expected
- # children we can stop right now
- return if expected_children.empty?
- # the parent role
- instance = pgProvisionedService.get(name)
- parent = instance.pgbindusers.all(:default_user => true)[0]
- # connect as the system user (not the parent or any of the
- # children) to ensure we don't have ACL problems
- connection = management_connection(instance, true)
- raise "Fail to connect to database #{name}" unless connection
- # figure out which children *actually* exist
- actual_children = get_actual_children connection, name, parent
- # log but ignore children that aren't both expected and actually exist
- children = expected_children & actual_children
- @logger.warn "Ignoring surplus children #{actual_children-children} in #{name}" unless (actual_children-children).empty?
- @logger.warn "Ignoring missing children #{expected_children-children} in #{name}" unless (expected_children-children).empty?
- # if there are no children, then there is nothing to do
- return if children.empty?
- # ensure that all children and in fact children of their parents
- unruly_children = get_unruly_children(connection, parent, children)
- unless unruly_children.empty?
- unruly_children.each do |u_c|
- connection.query("alter role #{u_c} inherit")
- connection.query("alter role #{u_c} set role=#{parent.user}")
- end
- connection.query("GRANT #{parent.user} TO #{unruly_children.join(',')};")
- @logger.info("New children #{unruly_children} of parent #{parent.user}")
- end
- # make all current objects owned by the parent
- connection.query("REASSIGN OWNED BY #{children.join(',')} TO #{parent.user};")
- rescue => x
- @logger.warn("Exception while managing object ownership: #{x}")
- ensure
- connection.close if connection
- end
-
- def manage_temp_privilege(name)
- instance = pgProvisionedService.get(name)
- return if instance.quota_exceeded
- connection = management_connection(instance, true)
- raise "Fail to connect to database #{name}" unless connection
- parent = pgProvisionedService.get(name).pgbindusers.all(:default_user => true)[0]
- connection.query("GRANT TEMP ON DATABASE #{name} TO #{parent.user}")
- connection.query("GRANT TEMP ON DATABASE #{name} TO #{parent.sys_user}")
- expected_children = get_expected_children name
- return expected_children if expected_children.empty?
- actual_children = get_actual_children connection, name, parent
- children = expected_children & actual_children
- @logger.warn "Ignoring surplus children #{actual_children-children} in #{name} when managing temp privilege" unless (actual_children-children).empty?
- @logger.warn "Ignoring missing children #{expected_children-children} in #{name} when managing temp privilege" unless (expected_children-children).empty?
- return if children.empty?
- # manage_object_ownership will make all unruly children be ruly children
- children.each do |i_c|
- connection.query("GRANT TEMP ON DATABASE #{name} TO #{i_c}")
- end
- rescue => x
- @logger.warn("Exception while managing temp privilege on database #{name}: #{x}")
- ensure
- connection.close if connection
- end
-
- def manage_create_privilege(name)
- instance = pgProvisionedService.get(name)
- return if instance.quota_exceeded
- connection = management_connection(instance, true)
- raise "Fail to connect to database #{name}" unless connection
- parent = pgProvisionedService.get(name).pgbindusers.all(:default_user => true)[0]
- connection.query("GRANT CREATE ON DATABASE #{name} TO #{parent.user}")
- rescue => x
- @logger.warn("Exception while managing create privilege on database #{name}: #{x}")
- ensure
- connection.close if connection
- end
-
- def manage_maxconnlimit(name)
- conn = fetch_global_connection name
- conn.query("update pg_database set datconnlimit=#{@max_db_conns} where datname='#{name}' and datconnlimit=-1")
- rescue => x
- @logger.warn("Exception while managing maxconnlimit on database #{name}: #{x}")
- end
-
# global connection is a persistent connection to postgresql server
# each version has one which shard by all instances
def init_global_connection(instance)
View
294 ng/postgresql/spec/postgresql_node_spec.rb
@@ -960,300 +960,6 @@ class << @node
end
end
- it "should get expected children correctly" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- bind = @node.bind @db['name'], @default_opts
- children = @node.get_expected_children @db['name']
- children.index(bind['user']).should_not == nil
- children.index(@db['user']).should == nil
- EM.stop
- end
- end
-
- it "should get actual children correctly" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- # sys_user is not return from provision/bind response
- # so only set user for parent
- parent = Node.pgBindUserClass(@opts[:use_warden]).new
- parent.user = @db['user']
- parent.password = @db['password']
- @db['user'] = @opts[:postgresql][@default_version]['user']
- @db['password'] = @opts[:postgresql][@default_version]['pass']
- sys_conn = connect_to_postgresql @db
- user = @node.bind @db['name'], @default_opts
-
- # this parent does not contain sys_user
- children = @node.get_actual_children sys_conn, @db['name'], parent
- sys_conn.close if sys_conn
- children.index('').should == nil
- children.index(parent.user).should == nil
- children.index(@opts[:postgresql][@default_version]['user']).should == nil
- children.index(user['user']).should_not == nil
- # should only have 2 sys_user in children
- # one for parent and the other for new binding
- num_sys_user = 0
- children.each do |child|
- num_sys_user+=1 if child.index 'su'
- end
- num_sys_user.should == 2
-
- # reset @db or we will miss to unprovision it
- @db['user'] = parent.user
- @db['password'] = parent.password
-
- EM.stop
- end
- end
-
- it "should get unruly children correctly" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- parent = VCAP::Services::Postgresql::Node::Binduser.new
- parent.user = @db['user']
- parent.password = @db['password']
- bind1 = @node.bind @db['name'], @default_opts
- bind2 = VCAP::Services::Postgresql::Node::Binduser.new
- bind2.user = "u-#{UUIDTools::UUID.random_create.to_s}".gsub(/-/, '')
-
- @db['user'] = @opts[:postgresql][@default_version]['user']
- @db['password'] = @opts[:postgresql][@default_version]['pass']
- sys_conn = connect_to_postgresql @db
- sys_conn.query "create role #{bind2.user}"
-
- children = []
- children << bind1['user']
- children << bind2['user']
- unruly_children = @node.get_unruly_children sys_conn, parent, children
- sys_conn.close if sys_conn
- unruly_children.index(bind1['user']).should == nil
- unruly_children.index(bind2['user']).should_not == nil
-
- #reset @db
- @db['user'] = parent.user
- @db['password'] = parent.password
- EM.stop
- end
- end
-
- it "should be able to migrate(max_conns_limit) legacy instances" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- conn = @node.fetch_global_connection(@default_version)
- ori_db_info = @node.get_db_info(conn, @db['name'])
- ori_limit = ori_db_info['datconnlimit']
- ori_limit.should_not == '-1'
- conn.query("update pg_database set datconnlimit=-1 where datname = '#{@db['name']}'")
- @node.get_db_info(conn, @db['name'])['datconnlimit'].should == '-1'
-
- node = VCAP::Services::Postgresql::Node.new(@opts)
- EM.add_timer(2) do
- conn_new= node.fetch_global_connection(@default_version)
-
- EM.add_timer(0.1) {
- @node.get_db_info(conn, @db['name'])['datconnlimit'].should == ori_limit
- node.get_db_info(conn_new, @db['name'])['datconnlimit'].should == ori_limit
- EM.stop
- }
- end
- end
- end
-
- it "should be able to migrate(grant create privilege) legacy instances" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- parent = @db['user']
- parent_password = @db['password']
- user1 = @node.bind(@db['name'], @default_opts)
-
- @db['user'] = @opts[:postgresql][@default_version]['user']
- @db['password'] = @opts[:postgresql][@default_version]['pass']
- sys_conn = connect_to_postgresql @db
-
- sys_conn.query "revoke create on database #{@db['name']} from #{parent}"
- sys_conn.close if sys_conn
-
- # reset @db
- @db['user'] = parent
- @db['password'] = parent_password
-
- # connect to the db and fail to create schema
- parent_conn = connect_to_postgresql @db
- expect { parent_conn.query('create schema parent_schema') }. should raise_error(PGError)
- parent_conn.close if parent_conn
-
- user1_conn = connect_to_postgresql user1
- expect { user1_conn.query('create schema user1_schema') }.should raise_error(PGError)
- user1_conn.close if user1_conn
-
- # create a new node to migrate
- node = VCAP::Services::Postgresql::Node.new(@opts)
- sleep 1
- EM.add_timer(0.1) {
- user1_conn = connect_to_postgresql user1
- expect { user1_conn.query('create schema user1_schema') }.should_not raise_error(PGError)
- expect { user1_conn.query('create table user1_schema.user1_table (value text)') }.should_not raise_error(PGError)
- user1_conn.close if user1_conn
- user2 = @node.bind(@db['name'], @default_opts)
- user2_conn = connect_to_postgresql user2
- expect { user2_conn.query('select * from user1_schema.user1_table') }.should_not raise_error(PGError)
- expect { user2_conn.query("insert into user1_schema.user1_table values('hello')") }.should_not raise_error(PGError)
- user2_conn.close if user2_conn
- EM.stop
- }
- end
-
- end
-
- it "should be able to migrate(grant temp privilege) legacy instances" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- parent = @db['user']
- parent_password = @db['password']
- user1 = @node.bind(@db['name'], @default_opts)
- user2 = @node.bind(@db['name'], @default_opts)
- orphan = @node.bind(@db['name'], @default_opts)
-
- @db['user'] = @opts[:postgresql][@default_version]['user']
- @db['password'] = @opts[:postgresql][@default_version]['pass']
- sys_conn = connect_to_postgresql @db
-
- sys_conn.query "revoke temp on database #{@db['name']} from #{parent}"
- sys_conn.query "revoke temp on database #{@db['name']} from #{user1['user']}"
- sys_conn.query "revoke temp on database #{@db['name']} from #{user2['user']}"
-
- sys_conn.query "revoke all on database #{@db['name']} from #{orphan['user']} cascade"
- sys_conn.query "drop role #{orphan['user']}"
-
- sys_conn.close if sys_conn
-
- # reset @db
- @db['user'] = parent
- @db['password'] = parent_password
-
- # connect to the db and fail to create temporary table/sequence/view
- parent_conn = connect_to_postgresql @db
- parent_conn.query('create table parent_table(id int, data text)')
- expect { parent_conn.query('create temporary table parent_temp_table as select * from parent_table') }.should raise_error(PGError)
- expect { parent_conn.query('create temporary sequence test_seq start 101') }.should raise_error(PGError)
- parent_conn.close if parent_conn
- user1_conn = connect_to_postgresql user1
- expect { user1_conn.query('select * into temporary user1_temp_table from parent_table') }.should raise_error(PGError)
- user1_conn.close if user1_conn
- user2_conn = connect_to_postgresql user2
- expect { user2_conn.query('create temporary view user2_temp_view as select * from parent_table') }.should raise_error(PGError)
- user2_conn.close if user2_conn
-
- # create a new node to migrate
- node = VCAP::Services::Postgresql::Node.new(@opts)
- sleep 1
- EM.add_timer(0.1) {
- parent_conn = connect_to_postgresql @db
- expect { parent_conn.query('create temporary table parent_temp_table as select * from parent_table') }.should_not raise_error(PGError)
- expect { parent_conn.query('create temporary sequence test_seq start 101') }.should_not raise_error(PGError)
- parent_conn.close if parent_conn
- user1_conn = connect_to_postgresql user1
- expect { user1_conn.query('select * into temporary user1_temp_table from parent_table') }.should_not raise_error(PGError)
- user1_conn.close if user1_conn
- user2_conn = connect_to_postgresql user2
- expect { user2_conn.query('create temporary view user2_temp_view as select * from parent_table') }.should_not raise_error(PGError)
- user2_conn.close if user2_conn
- EM.stop
- }
- end
- end
-
- it "should be able to migrate(manage object owner) legacy instances" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- parent = @db['user']
- parent_password = @db['password']
- # create a regular user through node
- user1 = @node.bind(@db['name'], @default_opts)
- # connect to the db with sys credential to 'revoke' the user's role
- # from parent to itself, to simulate a 'pre-r8' binding
- @db['user'] = @opts[:postgresql][@default_version]['user']
- @db['password'] = @opts[:postgresql][@default_version]['pass']
- sys_conn = connect_to_postgresql @db
- sys_conn.query "alter role #{user1['user']} noinherit"
- sys_conn.query "revoke #{parent} from #{user1['user']} cascade"
- sys_conn.close if sys_conn
-
- # reset @db
- @db['user'] = parent
- @db['password'] = parent_password
-
- # connect to the db with revoked user
- conn1 = connect_to_postgresql user1
- conn1.query 'create table t1(i int)'
- conn1.close if conn1
-
- user2 = @node.bind(@db['name'], @default_opts)
- conn2 = connect_to_postgresql user2
- expect { conn2.query 'drop table t1' }.should raise_error
- conn2.query 'create table t2(i int)'
-
- # new a node class to do migration work
- node = VCAP::Services::Postgresql::Node.new(@opts)
- sleep 1
- EM.add_timer(0.1) {
- expect { conn2.query 'drop table t1' }.should_not raise_error
- conn1 = connect_to_postgresql user1
- expect { conn1.query 'drop table t2' }.should_not raise_error
- conn1.query 'create table tt1(i int)'
- conn1.close if conn1
- expect { conn2.query 'drop table tt1' }.should_not raise_error
- conn2.close if conn2
- EM.stop
- }
- end
- end
-
- it "should migrate(manage object owner) legacy instances, even there is *orphan* user" do
- pending "Use warden, won't run this case." if @opts[:use_warden]
- EM.run do
- parent = @db['user']
- parent_password = @db['password']
- # create a regular user through node
- user1 = @node.bind(@db['name'], @default_opts)
- # connect to the db with sys credential to 'revoke' the user's role
- # from parent to itself, to simulate a 'pre-r8' binding
- @db['user'] = @opts[:postgresql][@default_version]['user']
- @db['password'] = @opts[:postgresql][@default_version]['pass']
- sys_conn = connect_to_postgresql @db
- sys_conn.query "alter role #{user1['user']} noinherit"
- sys_conn.query "revoke #{parent} from #{user1['user']} cascade"
- # connect to the db with revoked user
- conn1 = connect_to_postgresql user1
- conn1.query 'create table t(i int)'
- conn1.close if conn1
-
- user2 = @node.bind(@db['name'], @default_opts)
- conn2 = connect_to_postgresql user2
- expect { conn2.query 'drop table t' }.should raise_error
-
- # create an orphan binding
- # i.e. it's in local sqlite but not in pg server
- orphan = @node.bind(@db['name'], @default_opts)
- sys_conn.query "revoke all on database #{@db['name']} from #{orphan['user']} cascade"
- sys_conn.query "drop role #{orphan['user']}"
- sys_conn.close if sys_conn
-
- # reset @db
- @db['user'] = parent
- @db['password'] = parent_password
-
- # new a node class to do migration work
- node = VCAP::Services::Postgresql::Node.new(@opts)
- sleep 1
- EM.add_timer(0.1) {
- expect { conn2.query 'drop table t' }.should_not raise_error
- EM.stop
- }
- end
- end
-
it "should work that user2 can bring the db back to normal after user1 puts much data to cause quota enforced" do
node = nil
EM.run do
Please sign in to comment.
Something went wrong with that request. Please try again.