From 582dbd1a9f079bd741198c2437aaa1d3ca00b96d Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Sat, 18 Feb 2017 14:38:04 +0200 Subject: [PATCH 01/18] Adding the tenancy to cdn, deliveryservice and tmuser --- .../20170221000001_initial_tenancy.sql | 57 ++++++++ traffic_ops/app/db/seeds.sql | 7 +- traffic_ops/app/lib/Fixtures/Cdn.pm | 2 + .../app/lib/Fixtures/Deliveryservice.pm | 13 ++ .../app/lib/Fixtures/Integration/Cdn.pm | 2 + .../Fixtures/Integration/Deliveryservice.pm | 8 + .../app/lib/Fixtures/Integration/Tenant.pm | 55 +++++++ .../app/lib/Fixtures/Integration/TmUser.pm | 6 + traffic_ops/app/lib/Fixtures/Tenant.pm | 44 ++++++ traffic_ops/app/lib/Fixtures/TmUser.pm | 7 + traffic_ops/app/lib/Schema/Result/Cdn.pm | 37 ++++- .../app/lib/Schema/Result/Deliveryservice.pm | 41 +++++- .../app/lib/Schema/Result/GooseDbVersion.pm | 19 ++- traffic_ops/app/lib/Schema/Result/Tenant.pm | 138 ++++++++++++++++++ traffic_ops/app/lib/Schema/Result/TmUser.pm | 41 +++++- .../app/lib/Test/IntegrationTestHelper.pm | 3 + traffic_ops/app/lib/Test/TestHelper.pm | 3 + traffic_ops/app/t/api/1.2/cachegroup.t | 1 + .../app/t/api/1.2/federation_external.t | 1 + traffic_ops/app/t/api/1.2/parameter.t | 1 + traffic_ops/app/t/api/1.2/profile_parameter.t | 1 + 21 files changed, 459 insertions(+), 28 deletions(-) create mode 100644 traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql create mode 100644 traffic_ops/app/lib/Fixtures/Integration/Tenant.pm create mode 100644 traffic_ops/app/lib/Fixtures/Tenant.pm create mode 100644 traffic_ops/app/lib/Schema/Result/Tenant.pm diff --git a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql new file mode 100644 index 0000000000..9047281f90 --- /dev/null +++ b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql @@ -0,0 +1,57 @@ +/* + + 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. +*/ + +-- +goose Up +-- SQL in section 'Up' is executed when this migration is applied + +-- tenant +CREATE TABLE tenant ( + id bigint primary key NOT NULL, + name text UNIQUE NOT NULL, + last_updated timestamp with time zone DEFAULT now() +); + +CREATE TRIGGER on_update_current_timestamp BEFORE UPDATE ON tenant FOR EACH ROW EXECUTE PROCEDURE on_update_current_timestamp_last_updated(); + +ALTER TABLE tm_user + ADD tenant_id BIGINT NOT NULL, + ADD CONSTRAINT tenantidfk FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, + ALTER COLUMN tenant_id SET DEFAULT 1; + +ALTER TABLE cdn + ADD tenant_id BIGINT NOT NULL, + ADD CONSTRAINT tenantidfk FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, + ALTER COLUMN tenant_id SET DEFAULT 1; + +ALTER TABLE deliveryservice + ADD tenant_id BIGINT NOT NULL, + ADD CONSTRAINT tenantidfk FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, + ALTER COLUMN tenant_id SET DEFAULT 1; + +-- +goose Down +-- SQL section 'Down' is executed when this migration is rolled back + +ALTER TABLE deliveryservice +DROP COLUMN tenant_id; + +ALTER TABLE cdn +DROP COLUMN tenant_id; + +ALTER TABLE tm_user +DROP COLUMN tenant_id; + +DROP TRIGGER on_update_current_timestamp ON tenant; + +DROP TABLE tenant; diff --git a/traffic_ops/app/db/seeds.sql b/traffic_ops/app/db/seeds.sql index 16a69640f9..7a793bce94 100644 --- a/traffic_ops/app/db/seeds.sql +++ b/traffic_ops/app/db/seeds.sql @@ -148,9 +148,12 @@ insert into profile_parameter (profile, parameter) values ( ) ON CONFLICT DO NOTHING; update server set https_port = 443 where https_port is null; +-- tenant +insert into tenant (id, name) values (1, 'none') ON CONFLICT DO NOTHING; + -- users -insert into tm_user (username, role,full_name) values ('portal',(select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; -insert into tm_user (username, role, full_name, token) values ('extension', 3, 'Extension User, DO NOT DELETE', '91504CE6-8E4A-46B2-9F9F-FE7C15228498') ON CONFLICT DO NOTHING; +insert into tm_user (username, tenant_id, role,full_name) values ('portal', 1, (select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; +insert into tm_user (username, tenant_id, role, full_name, token) values ('extension', 1, 3, 'Extension User, DO NOT DELETE', '91504CE6-8E4A-46B2-9F9F-FE7C15228498') ON CONFLICT DO NOTHING; -- to extensions -- some of the old ones do not get a new place, and there will be 'gaps' in the column usage.... New to_extension add will have to take care of that. diff --git a/traffic_ops/app/lib/Fixtures/Cdn.pm b/traffic_ops/app/lib/Fixtures/Cdn.pm index 4ca30b4dd5..c2dbf29b0e 100644 --- a/traffic_ops/app/lib/Fixtures/Cdn.pm +++ b/traffic_ops/app/lib/Fixtures/Cdn.pm @@ -25,6 +25,7 @@ my %definition_for = ( using => { id => 100, name => 'cdn1', + tenant_id => 1, }, }, ## id => 2 @@ -33,6 +34,7 @@ my %definition_for = ( using => { id => 200, name => 'cdn2', + tenant_id => 1, }, }, ); diff --git a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm index 28953d0ed8..ca10fe9160 100644 --- a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm +++ b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm @@ -25,6 +25,7 @@ my %definition_for = ( id => 100, xml_id => 'test-ds1', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -63,6 +64,7 @@ my %definition_for = ( id => 200, xml_id => 'test-ds2', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -101,6 +103,7 @@ my %definition_for = ( id => 300, xml_id => 'test-ds3', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -139,6 +142,7 @@ my %definition_for = ( id => 400, xml_id => 'test-ds4', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -177,6 +181,7 @@ my %definition_for = ( id => 500, xml_id => 'test-ds5', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -215,6 +220,7 @@ my %definition_for = ( id => 600, xml_id => 'test-ds6', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -253,6 +259,7 @@ my %definition_for = ( id => 700, xml_id => 'steering-ds1', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -289,6 +296,7 @@ my %definition_for = ( id => 800, xml_id => 'steering-ds2', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -325,6 +333,7 @@ my %definition_for = ( id => 900, xml_id => 'steering-ds3', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -361,6 +370,7 @@ my %definition_for = ( id => 1000, xml_id => 'steering-target-ds1', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -397,6 +407,7 @@ my %definition_for = ( id => 1100, xml_id => 'steering-target-ds2', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -433,6 +444,7 @@ my %definition_for = ( id => 1200, xml_id => 'steering-target-ds3', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, @@ -469,6 +481,7 @@ my %definition_for = ( id => 1300, xml_id => 'steering-target-ds4', active => 1, + tenant_id => 1, dscp => 40, signed => 0, qstring_ignore => 0, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm b/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm index d3a7eefb84..0dd2ffffd3 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm @@ -31,6 +31,7 @@ my %definition_for = ( new => 'Cdn', using => { name => 'cdn_number_1', + tenant_id => '1', dnssec_enabled => '0', last_updated => '2015-12-10 15:43:45', }, @@ -40,6 +41,7 @@ my %definition_for = ( new => 'Cdn', using => { name => 'cdn_number_2', + tenant_id => '1', dnssec_enabled => '0', last_updated => '2015-12-10 15:43:45', }, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm index 65c96158b4..c2656f5c2b 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm @@ -63,6 +63,7 @@ my %definition_for = ( global_max_tps => '0', max_dns_answers => '0', tr_response_headers => undef, + tenant_id => '1', cdn_id => '2', dns_bypass_ttl => undef, initial_dispersion => '1', @@ -101,6 +102,7 @@ my %definition_for = ( type => '10', ipv6_routing_enabled => undef, tr_response_headers => undef, + tenant_id => '1', cdn_id => '1', global_max_mbps => '0', global_max_tps => '0', @@ -170,6 +172,7 @@ my %definition_for = ( remap_text => undef, ssl_key_version => '0', tr_request_headers => undef, + tenant_id => '1', cdn_id => '1', global_max_tps => '0', http_bypass_fqdn => '', @@ -195,6 +198,7 @@ my %definition_for = ( last_updated => '2015-12-10 15:44:37', signed => '0', ccr_dns_ttl => '3600', + tenant_id => '1', cdn_id => '1', display_name => 'movies-c1', protocol => '0', @@ -273,6 +277,7 @@ my %definition_for = ( global_max_tps => '0', http_bypass_fqdn => '', origin_shield => undef, + tenant_id => '1', cdn_id => '1', dns_bypass_ttl => undef, }, @@ -291,6 +296,7 @@ my %definition_for = ( mid_header_rewrite => undef, signed => '0', ssl_key_version => '0', + tenant_id => '1', cdn_id => '2', long_desc => 'test-ds3 long_desc', max_dns_answers => '0', @@ -339,6 +345,7 @@ my %definition_for = ( origin_shield => undef, range_request_handling => '0', remap_text => undef, + tenant_id => '1', cdn_id => '2', long_desc_2 => 'test long_desc_2', tr_request_headers => undef, @@ -401,6 +408,7 @@ my %definition_for = ( active => '1', cacheurl => undef, ccr_dns_ttl => '3600', + tenant_id => '1', cdn_id => '2', dns_bypass_ip => '', dns_bypass_ttl => undef, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm new file mode 100644 index 0000000000..9f86508daf --- /dev/null +++ b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm @@ -0,0 +1,55 @@ +package Fixtures::Integration::Tenant; + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + +# Do not edit! Generated code. +# See https://github.com/Comcast/traffic_control/wiki/The%20Kabletown%20example + +use Moose; +extends 'DBIx::Class::EasyFixture'; +use namespace::autoclean; + +my %definition_for = ( + ## id => 1 + '0' => { + new => 'Tenant', + using => { + id => 1, + name => 'tenant_number_1', + last_updated => '2015-12-10 15:43:45', + }, + }, +); + +sub name { + return "Tenant"; +} + +sub get_definition { + my ( $self, $name ) = @_; + return $definition_for{$name}; +} + +sub all_fixture_names { + # sort by db name to guarantee insertion order + return (sort { $definition_for{$a}{using}{name} cmp $definition_for{$b}{using}{name} } keys %definition_for); +} + +__PACKAGE__->meta->make_immutable; +1; diff --git a/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm b/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm index 0aa637a2dd..7218e459c2 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm @@ -31,6 +31,7 @@ my %definition_for = ( new => 'TmUser', using => { username => 'admin', + tenant_id => '1', local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', uid => '1', role => '1', @@ -59,6 +60,7 @@ my %definition_for = ( company => undef, confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', username => 'codebig', + tenant_id => '1', gid => '1', country => 'United States', state_or_province => 'state_or_province', @@ -86,6 +88,7 @@ my %definition_for = ( confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', gid => '1', username => 'extension', + tenant_id => '1', country => 'United States', email => 'plugin@email.com', state_or_province => 'state_or_province', @@ -111,6 +114,7 @@ my %definition_for = ( confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', company => undef, username => 'migration', + tenant_id => '1', gid => '1', state_or_province => undef, email => undef, @@ -140,6 +144,7 @@ my %definition_for = ( country => 'United States', gid => '1', username => 'portal', + tenant_id => '1', confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', company => undef, local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', @@ -165,6 +170,7 @@ my %definition_for = ( state_or_province => 'state_or_province', gid => '1', username => 'testuser', + tenant_id => '1', last_updated => '2015-12-10 15:43:45', company => undef, confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', diff --git a/traffic_ops/app/lib/Fixtures/Tenant.pm b/traffic_ops/app/lib/Fixtures/Tenant.pm new file mode 100644 index 0000000000..7f6a7f26e7 --- /dev/null +++ b/traffic_ops/app/lib/Fixtures/Tenant.pm @@ -0,0 +1,44 @@ +package Fixtures::Tenant; +# +# +# 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. +# +use Moose; +extends 'DBIx::Class::EasyFixture'; +use namespace::autoclean; +use Digest::SHA1 qw(sha1_hex); + +my %definition_for = ( + ## id => 1 + tenant1_tenant_name => { + new => 'Tenant', + using => { + id => 1, + name => 'tenant1', + }, + }, +); + +sub get_definition { + my ( $self, $name ) = @_; + return $definition_for{$name}; +} + +sub all_fixture_names { + # sort by db id to guarantee insert order + return (sort { $definition_for{$a}{using}{id} cmp $definition_for{$b}{using}{id} } keys %definition_for); +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/traffic_ops/app/lib/Fixtures/TmUser.pm b/traffic_ops/app/lib/Fixtures/TmUser.pm index eee798606d..de70ba1900 100644 --- a/traffic_ops/app/lib/Fixtures/TmUser.pm +++ b/traffic_ops/app/lib/Fixtures/TmUser.pm @@ -27,6 +27,7 @@ my %definition_for = ( using => { id => 100, username => 'admin', + tenant_id => 1, role => 4, uid => '1', gid => '1', @@ -51,6 +52,7 @@ my %definition_for = ( using => { id => 200, username => 'portal', + tenant_id => 1, role => 6, uid => '1', gid => '1', @@ -75,6 +77,7 @@ my %definition_for = ( using => { id => 300, username => 'codebig', + tenant_id => 1, role => 6, uid => '1', gid => '1', @@ -99,6 +102,7 @@ my %definition_for = ( using => { id => 400, username => 'migration', + tenant_id => 1, role => 5, uid => '1', gid => '1', @@ -123,6 +127,7 @@ my %definition_for = ( using => { id => 500, username => 'federation', + tenant_id => 1, role => 7, uid => '1', gid => '1', @@ -147,6 +152,7 @@ my %definition_for = ( using => { id => 600, username => 'steering1', + tenant_id => 1, role => 7, uid => '1', gid => '1', @@ -171,6 +177,7 @@ my %definition_for = ( using => { id => 700, username => 'steering2', + tenant_id => 1, role => 7, uid => '1', gid => '1', diff --git a/traffic_ops/app/lib/Schema/Result/Cdn.pm b/traffic_ops/app/lib/Schema/Result/Cdn.pm index 39669cb52c..55368c9885 100644 --- a/traffic_ops/app/lib/Schema/Result/Cdn.pm +++ b/traffic_ops/app/lib/Schema/Result/Cdn.pm @@ -48,6 +48,13 @@ __PACKAGE__->table("cdn"); default_value: false is_nullable: 0 +=head2 tenant_id + + data_type: 'bigint' + default_value: 1 + is_foreign_key: 1 + is_nullable: 0 + =cut __PACKAGE__->add_columns( @@ -69,6 +76,13 @@ __PACKAGE__->add_columns( }, "dnssec_enabled", { data_type => "boolean", default_value => \"false", is_nullable => 0 }, + "tenant_id", + { + data_type => "bigint", + default_value => 1, + is_foreign_key => 1, + is_nullable => 0, + }, ); =head1 PRIMARY KEY @@ -85,7 +99,7 @@ __PACKAGE__->set_primary_key("id"); =head1 UNIQUE CONSTRAINTS -=head2 C +=head2 C =over 4 @@ -95,7 +109,7 @@ __PACKAGE__->set_primary_key("id"); =cut -__PACKAGE__->add_unique_constraint("idx_24701_cdn_cdn_unique", ["name"]); +__PACKAGE__->add_unique_constraint("idx_89491_cdn_cdn_unique", ["name"]); =head1 RELATIONS @@ -144,9 +158,24 @@ __PACKAGE__->might_have( { cascade_copy => 0, cascade_delete => 0 }, ); +=head2 tenant + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "tenant", + "Schema::Result::Tenant", + { id => "tenant_id" }, + { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, +); + -# Created by DBIx::Class::Schema::Loader v0.07042 @ 2016-12-09 09:10:09 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:yBLkvGMimI0emk0nO5/CAA +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:58 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:o/4nImKo2c2AAJqoFouQ6g # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm index afb187714e..7af0f6a80f 100644 --- a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm +++ b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm @@ -288,6 +288,13 @@ __PACKAGE__->table("deliveryservice"); data_type: 'text' is_nullable: 1 +=head2 tenant_id + + data_type: 'bigint' + default_value: 1 + is_foreign_key: 1 + is_nullable: 0 + =cut __PACKAGE__->add_columns( @@ -399,6 +406,13 @@ __PACKAGE__->add_columns( { data_type => "smallint", is_nullable => 1 }, "geolimit_redirect_url", { data_type => "text", is_nullable => 1 }, + "tenant_id", + { + data_type => "bigint", + default_value => 1, + is_foreign_key => 1, + is_nullable => 0, + }, ); =head1 PRIMARY KEY @@ -417,7 +431,7 @@ __PACKAGE__->set_primary_key("id", "type"); =head1 UNIQUE CONSTRAINTS -=head2 C +=head2 C =over 4 @@ -427,9 +441,9 @@ __PACKAGE__->set_primary_key("id", "type"); =cut -__PACKAGE__->add_unique_constraint("idx_54278_ds_id_unique", ["id"]); +__PACKAGE__->add_unique_constraint("idx_89502_ds_id_unique", ["id"]); -=head2 C +=head2 C =over 4 @@ -439,7 +453,7 @@ __PACKAGE__->add_unique_constraint("idx_54278_ds_id_unique", ["id"]); =cut -__PACKAGE__->add_unique_constraint("idx_54278_ds_name_unique", ["xml_id"]); +__PACKAGE__->add_unique_constraint("idx_89502_ds_name_unique", ["xml_id"]); =head1 RELATIONS @@ -593,6 +607,21 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); +=head2 tenant + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "tenant", + "Schema::Result::Tenant", + { id => "tenant_id" }, + { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, +); + =head2 type Type: belongs_to @@ -609,8 +638,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2016-11-18 22:45:19 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OGL35PLFXtZ8BuzgP7IHzQ +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:58 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:AVaUXbxO5Efc2Z05PQFINQ # You can replace this text with custom code or comments, and it will be preserved on regeneration # diff --git a/traffic_ops/app/lib/Schema/Result/GooseDbVersion.pm b/traffic_ops/app/lib/Schema/Result/GooseDbVersion.pm index d1dd43c619..3e3140dac9 100644 --- a/traffic_ops/app/lib/Schema/Result/GooseDbVersion.pm +++ b/traffic_ops/app/lib/Schema/Result/GooseDbVersion.pm @@ -25,25 +25,24 @@ __PACKAGE__->table("goose_db_version"); =head2 id - data_type: 'bigint' + data_type: 'integer' is_auto_increment: 1 is_nullable: 0 sequence: 'goose_db_version_id_seq' =head2 version_id - data_type: 'numeric' + data_type: 'bigint' is_nullable: 0 =head2 is_applied data_type: 'boolean' - default_value: false is_nullable: 0 =head2 tstamp - data_type: 'timestamp with time zone' + data_type: 'timestamp' default_value: current_timestamp is_nullable: 1 original: {default_value => \"now()"} @@ -53,18 +52,18 @@ __PACKAGE__->table("goose_db_version"); __PACKAGE__->add_columns( "id", { - data_type => "bigint", + data_type => "integer", is_auto_increment => 1, is_nullable => 0, sequence => "goose_db_version_id_seq", }, "version_id", - { data_type => "numeric", is_nullable => 0 }, + { data_type => "bigint", is_nullable => 0 }, "is_applied", - { data_type => "boolean", default_value => \"false", is_nullable => 0 }, + { data_type => "boolean", is_nullable => 0 }, "tstamp", { - data_type => "timestamp with time zone", + data_type => "timestamp", default_value => \"current_timestamp", is_nullable => 1, original => { default_value => \"now()" }, @@ -84,8 +83,8 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); -# Created by DBIx::Class::Schema::Loader v0.07045 @ 2016-11-15 09:35:47 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:kNbDG4yXcgqGB2dRrmtF6A +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 08:40:27 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Oq4ggGj9PfLOMWcjMhOc+Q # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/lib/Schema/Result/Tenant.pm b/traffic_ops/app/lib/Schema/Result/Tenant.pm new file mode 100644 index 0000000000..80a3821192 --- /dev/null +++ b/traffic_ops/app/lib/Schema/Result/Tenant.pm @@ -0,0 +1,138 @@ +use utf8; +package Schema::Result::Tenant; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +=head1 NAME + +Schema::Result::Tenant + +=cut + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +=head1 TABLE: C + +=cut + +__PACKAGE__->table("tenant"); + +=head1 ACCESSORS + +=head2 id + + data_type: 'bigint' + is_nullable: 0 + +=head2 name + + data_type: 'text' + is_nullable: 0 + +=head2 last_updated + + data_type: 'timestamp with time zone' + default_value: current_timestamp + is_nullable: 1 + original: {default_value => \"now()"} + +=cut + +__PACKAGE__->add_columns( + "id", + { data_type => "bigint", is_nullable => 0 }, + "name", + { data_type => "text", is_nullable => 0 }, + "last_updated", + { + data_type => "timestamp with time zone", + default_value => \"current_timestamp", + is_nullable => 1, + original => { default_value => \"now()" }, + }, +); + +=head1 PRIMARY KEY + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->set_primary_key("id"); + +=head1 UNIQUE CONSTRAINTS + +=head2 C + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint("tenant_name_key", ["name"]); + +=head1 RELATIONS + +=head2 cdns + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "cdns", + "Schema::Result::Cdn", + { "foreign.tenant_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + +=head2 deliveryservices + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "deliveryservices", + "Schema::Result::Deliveryservice", + { "foreign.tenant_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + +=head2 tm_users + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "tm_users", + "Schema::Result::TmUser", + { "foreign.tenant_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + + +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:59 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:GwD6l7apYu+ouFMo7Jv/Tg + + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +1; diff --git a/traffic_ops/app/lib/Schema/Result/TmUser.pm b/traffic_ops/app/lib/Schema/Result/TmUser.pm index bc0abc9b70..d45f6af3da 100644 --- a/traffic_ops/app/lib/Schema/Result/TmUser.pm +++ b/traffic_ops/app/lib/Schema/Result/TmUser.pm @@ -139,6 +139,13 @@ __PACKAGE__->table("tm_user"); data_type: 'timestamp with time zone' is_nullable: 1 +=head2 tenant_id + + data_type: 'bigint' + default_value: 1 + is_foreign_key: 1 + is_nullable: 0 + =cut __PACKAGE__->add_columns( @@ -196,6 +203,13 @@ __PACKAGE__->add_columns( { data_type => "text", is_nullable => 1 }, "registration_sent", { data_type => "timestamp with time zone", is_nullable => 1 }, + "tenant_id", + { + data_type => "bigint", + default_value => 1, + is_foreign_key => 1, + is_nullable => 0, + }, ); =head1 PRIMARY KEY @@ -212,7 +226,7 @@ __PACKAGE__->set_primary_key("id"); =head1 UNIQUE CONSTRAINTS -=head2 C +=head2 C =over 4 @@ -222,9 +236,9 @@ __PACKAGE__->set_primary_key("id"); =cut -__PACKAGE__->add_unique_constraint("idx_54541_tmuser_email_unique", ["email"]); +__PACKAGE__->add_unique_constraint("idx_89765_tmuser_email_unique", ["email"]); -=head2 C +=head2 C =over 4 @@ -234,7 +248,7 @@ __PACKAGE__->add_unique_constraint("idx_54541_tmuser_email_unique", ["email"]); =cut -__PACKAGE__->add_unique_constraint("idx_54541_username_unique", ["username"]); +__PACKAGE__->add_unique_constraint("idx_89765_username_unique", ["username"]); =head1 RELATIONS @@ -318,9 +332,24 @@ __PACKAGE__->belongs_to( }, ); +=head2 tenant + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "tenant", + "Schema::Result::Tenant", + { id => "tenant_id" }, + { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, +); + -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2016-11-18 22:45:19 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SGrZkCGhmXed7UzWMLYfOg +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:59 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:YyD7RMTRTQfgdo/uLgeezQ # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/lib/Test/IntegrationTestHelper.pm b/traffic_ops/app/lib/Test/IntegrationTestHelper.pm index 1abf9d2011..e49a5611ae 100644 --- a/traffic_ops/app/lib/Test/IntegrationTestHelper.pm +++ b/traffic_ops/app/lib/Test/IntegrationTestHelper.pm @@ -58,6 +58,7 @@ use Fixtures::Integration::Server; use Fixtures::Integration::Staticdnsentry; use Fixtures::Integration::StatsSummary; use Fixtures::Integration::Status; +use Fixtures::Integration::Tenant; use Fixtures::Integration::TmUser; use Fixtures::Integration::ToExtension; use Fixtures::Integration::Type; @@ -134,6 +135,7 @@ sub load_core_data { $self->reset_sequence_id(); diag "Initializing DB:"; + $self->load_all_fixtures( Fixtures::Integration::Tenant->new($schema_values) ); $self->load_all_fixtures( Fixtures::Integration::Cdn->new($schema_values) ); $self->load_all_fixtures( Fixtures::Integration::Type->new($schema_values) ); $self->load_all_fixtures( Fixtures::Integration::Role->new($schema_values) ); @@ -193,6 +195,7 @@ sub unload_core_data { $self->teardown( $schema, 'Division' ); $self->teardown( $schema, 'Snapshot' ); $self->teardown( $schema, 'Cdn' ); + $self->teardown( $schema, 'Tenant' ); } 1; diff --git a/traffic_ops/app/lib/Test/TestHelper.pm b/traffic_ops/app/lib/Test/TestHelper.pm index 2b53dcd918..2e85c7c5b4 100644 --- a/traffic_ops/app/lib/Test/TestHelper.pm +++ b/traffic_ops/app/lib/Test/TestHelper.pm @@ -37,6 +37,7 @@ use Fixtures::ProfileParameter; use Fixtures::Role; use Fixtures::Server; use Fixtures::Status; +use Fixtures::Tenant; use Fixtures::TmUser; use Fixtures::Type; use Fixtures::Division; @@ -103,6 +104,7 @@ sub load_core_data { $self->reset_sequence_id(); + $self->load_all_fixtures( Fixtures::Tenant->new($schema_values) ); $self->load_all_fixtures( Fixtures::Cdn->new($schema_values) ); $self->load_all_fixtures( Fixtures::Role->new($schema_values) ); $self->load_all_fixtures( Fixtures::TmUser->new($schema_values) ); @@ -153,6 +155,7 @@ sub unload_core_data { $self->teardown($schema, 'Status'); $self->teardown( $schema, 'Snapshot' ); $self->teardown($schema, 'Cdn'); + $self->teardown($schema, 'Tenant'); } sub teardown { diff --git a/traffic_ops/app/t/api/1.2/cachegroup.t b/traffic_ops/app/t/api/1.2/cachegroup.t index a03b2266d5..caa943e684 100644 --- a/traffic_ops/app/t/api/1.2/cachegroup.t +++ b/traffic_ops/app/t/api/1.2/cachegroup.t @@ -37,6 +37,7 @@ Test::TestHelper->unload_core_data($schema); # Load the test data up until 'cachegroup', because this test case creates # them. +Test::TestHelper->load_all_fixtures( Fixtures::Tenant->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Cdn->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Role->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::TmUser->new($schema_values) ); diff --git a/traffic_ops/app/t/api/1.2/federation_external.t b/traffic_ops/app/t/api/1.2/federation_external.t index e9959f60f1..2f2279f1f3 100644 --- a/traffic_ops/app/t/api/1.2/federation_external.t +++ b/traffic_ops/app/t/api/1.2/federation_external.t @@ -39,6 +39,7 @@ my $schema_values = { schema => $schema, no_transactions => 1 }; #unload data for a clean test Test::TestHelper->unload_core_data($schema); +Test::TestHelper->load_all_fixtures( Fixtures::Tenant->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Cdn->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Role->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::TmUser->new($schema_values) ); diff --git a/traffic_ops/app/t/api/1.2/parameter.t b/traffic_ops/app/t/api/1.2/parameter.t index 8845b19be8..0458f4cb69 100644 --- a/traffic_ops/app/t/api/1.2/parameter.t +++ b/traffic_ops/app/t/api/1.2/parameter.t @@ -38,6 +38,7 @@ Test::TestHelper->unload_core_data($schema); # Load the test data up until 'cachegroup', because this test case creates # them. +Test::TestHelper->load_all_fixtures( Fixtures::Tenant->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Cdn->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Role->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::TmUser->new($schema_values) ); diff --git a/traffic_ops/app/t/api/1.2/profile_parameter.t b/traffic_ops/app/t/api/1.2/profile_parameter.t index 9a1215b929..dd3bf83fe3 100644 --- a/traffic_ops/app/t/api/1.2/profile_parameter.t +++ b/traffic_ops/app/t/api/1.2/profile_parameter.t @@ -35,6 +35,7 @@ my $dbh = Schema->database_handle; my $t = Test::Mojo->new('TrafficOps'); Test::TestHelper->unload_core_data($schema); +Test::TestHelper->load_all_fixtures( Fixtures::Tenant->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Cdn->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::Role->new($schema_values) ); Test::TestHelper->load_all_fixtures( Fixtures::TmUser->new($schema_values) ); From 7471ce81ac3258c4712ed7874a73df3d7058a930 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Sat, 18 Feb 2017 15:29:02 +0200 Subject: [PATCH 02/18] Add a parent tenant capability, as well as the built-in root and guest tenants --- .../20170221000001_initial_tenancy.sql | 14 +++--- traffic_ops/app/db/seeds.sql | 7 +-- traffic_ops/app/lib/Fixtures/Cdn.pm | 4 +- .../app/lib/Fixtures/Deliveryservice.pm | 26 +++++----- .../app/lib/Fixtures/Integration/Cdn.pm | 4 +- .../Fixtures/Integration/Deliveryservice.pm | 16 +++---- .../app/lib/Fixtures/Integration/Tenant.pm | 10 ++++ .../app/lib/Fixtures/Integration/TmUser.pm | 12 ++--- traffic_ops/app/lib/Fixtures/Tenant.pm | 10 +++- traffic_ops/app/lib/Fixtures/TmUser.pm | 14 +++--- traffic_ops/app/lib/Schema/Result/Cdn.pm | 8 ++-- .../app/lib/Schema/Result/Deliveryservice.pm | 8 ++-- traffic_ops/app/lib/Schema/Result/Tenant.pm | 47 ++++++++++++++++++- traffic_ops/app/lib/Schema/Result/TmUser.pm | 8 ++-- 14 files changed, 126 insertions(+), 62 deletions(-) diff --git a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql index 9047281f90..3d6dc2449a 100644 --- a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql +++ b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql @@ -20,6 +20,8 @@ CREATE TABLE tenant ( id bigint primary key NOT NULL, name text UNIQUE NOT NULL, + parent_id bigint, + CONSTRAINT fk_parentid FOREIGN KEY (parent_id) REFERENCES tenant(id) ON DELETE CASCADE, last_updated timestamp with time zone DEFAULT now() ); @@ -27,18 +29,18 @@ CREATE TRIGGER on_update_current_timestamp BEFORE UPDATE ON tenant FOR EACH ROW ALTER TABLE tm_user ADD tenant_id BIGINT NOT NULL, - ADD CONSTRAINT tenantidfk FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, - ALTER COLUMN tenant_id SET DEFAULT 1; + ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, + ALTER COLUMN tenant_id SET DEFAULT 2; ALTER TABLE cdn ADD tenant_id BIGINT NOT NULL, - ADD CONSTRAINT tenantidfk FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, - ALTER COLUMN tenant_id SET DEFAULT 1; + ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, + ALTER COLUMN tenant_id SET DEFAULT 2; ALTER TABLE deliveryservice ADD tenant_id BIGINT NOT NULL, - ADD CONSTRAINT tenantidfk FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, - ALTER COLUMN tenant_id SET DEFAULT 1; + ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, + ALTER COLUMN tenant_id SET DEFAULT 2; -- +goose Down -- SQL section 'Down' is executed when this migration is rolled back diff --git a/traffic_ops/app/db/seeds.sql b/traffic_ops/app/db/seeds.sql index 7a793bce94..189fb189a8 100644 --- a/traffic_ops/app/db/seeds.sql +++ b/traffic_ops/app/db/seeds.sql @@ -149,11 +149,12 @@ insert into profile_parameter (profile, parameter) values ( update server set https_port = 443 where https_port is null; -- tenant -insert into tenant (id, name) values (1, 'none') ON CONFLICT DO NOTHING; +insert into tenant (id, name) values (1, 'root') ON CONFLICT DO NOTHING; +insert into tenant (id, name, parent_id) values (2, 'guest', 1) ON CONFLICT DO NOTHING; -- users -insert into tm_user (username, tenant_id, role,full_name) values ('portal', 1, (select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; -insert into tm_user (username, tenant_id, role, full_name, token) values ('extension', 1, 3, 'Extension User, DO NOT DELETE', '91504CE6-8E4A-46B2-9F9F-FE7C15228498') ON CONFLICT DO NOTHING; +insert into tm_user (username, tenant_id, role,full_name) values ('portal', 2, (select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; +insert into tm_user (username, tenant_id, role, full_name, token) values ('extension', 2, 3, 'Extension User, DO NOT DELETE', '91504CE6-8E4A-46B2-9F9F-FE7C15228498') ON CONFLICT DO NOTHING; -- to extensions -- some of the old ones do not get a new place, and there will be 'gaps' in the column usage.... New to_extension add will have to take care of that. diff --git a/traffic_ops/app/lib/Fixtures/Cdn.pm b/traffic_ops/app/lib/Fixtures/Cdn.pm index c2dbf29b0e..f658544425 100644 --- a/traffic_ops/app/lib/Fixtures/Cdn.pm +++ b/traffic_ops/app/lib/Fixtures/Cdn.pm @@ -25,7 +25,7 @@ my %definition_for = ( using => { id => 100, name => 'cdn1', - tenant_id => 1, + tenant_id => 2, }, }, ## id => 2 @@ -34,7 +34,7 @@ my %definition_for = ( using => { id => 200, name => 'cdn2', - tenant_id => 1, + tenant_id => 2, }, }, ); diff --git a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm index ca10fe9160..57f7466c55 100644 --- a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm +++ b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm @@ -25,7 +25,7 @@ my %definition_for = ( id => 100, xml_id => 'test-ds1', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -64,7 +64,7 @@ my %definition_for = ( id => 200, xml_id => 'test-ds2', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -103,7 +103,7 @@ my %definition_for = ( id => 300, xml_id => 'test-ds3', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -142,7 +142,7 @@ my %definition_for = ( id => 400, xml_id => 'test-ds4', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -181,7 +181,7 @@ my %definition_for = ( id => 500, xml_id => 'test-ds5', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -220,7 +220,7 @@ my %definition_for = ( id => 600, xml_id => 'test-ds6', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -259,7 +259,7 @@ my %definition_for = ( id => 700, xml_id => 'steering-ds1', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -296,7 +296,7 @@ my %definition_for = ( id => 800, xml_id => 'steering-ds2', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -333,7 +333,7 @@ my %definition_for = ( id => 900, xml_id => 'steering-ds3', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -370,7 +370,7 @@ my %definition_for = ( id => 1000, xml_id => 'steering-target-ds1', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -407,7 +407,7 @@ my %definition_for = ( id => 1100, xml_id => 'steering-target-ds2', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -444,7 +444,7 @@ my %definition_for = ( id => 1200, xml_id => 'steering-target-ds3', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, @@ -481,7 +481,7 @@ my %definition_for = ( id => 1300, xml_id => 'steering-target-ds4', active => 1, - tenant_id => 1, + tenant_id => 2, dscp => 40, signed => 0, qstring_ignore => 0, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm b/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm index 0dd2ffffd3..4480ddd420 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm @@ -31,7 +31,7 @@ my %definition_for = ( new => 'Cdn', using => { name => 'cdn_number_1', - tenant_id => '1', + tenant_id => '2', dnssec_enabled => '0', last_updated => '2015-12-10 15:43:45', }, @@ -41,7 +41,7 @@ my %definition_for = ( new => 'Cdn', using => { name => 'cdn_number_2', - tenant_id => '1', + tenant_id => '2', dnssec_enabled => '0', last_updated => '2015-12-10 15:43:45', }, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm index c2656f5c2b..1c729fb820 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm @@ -63,7 +63,7 @@ my %definition_for = ( global_max_tps => '0', max_dns_answers => '0', tr_response_headers => undef, - tenant_id => '1', + tenant_id => '2', cdn_id => '2', dns_bypass_ttl => undef, initial_dispersion => '1', @@ -102,7 +102,7 @@ my %definition_for = ( type => '10', ipv6_routing_enabled => undef, tr_response_headers => undef, - tenant_id => '1', + tenant_id => '2', cdn_id => '1', global_max_mbps => '0', global_max_tps => '0', @@ -172,7 +172,7 @@ my %definition_for = ( remap_text => undef, ssl_key_version => '0', tr_request_headers => undef, - tenant_id => '1', + tenant_id => '2', cdn_id => '1', global_max_tps => '0', http_bypass_fqdn => '', @@ -198,7 +198,7 @@ my %definition_for = ( last_updated => '2015-12-10 15:44:37', signed => '0', ccr_dns_ttl => '3600', - tenant_id => '1', + tenant_id => '2', cdn_id => '1', display_name => 'movies-c1', protocol => '0', @@ -277,7 +277,7 @@ my %definition_for = ( global_max_tps => '0', http_bypass_fqdn => '', origin_shield => undef, - tenant_id => '1', + tenant_id => '2', cdn_id => '1', dns_bypass_ttl => undef, }, @@ -296,7 +296,7 @@ my %definition_for = ( mid_header_rewrite => undef, signed => '0', ssl_key_version => '0', - tenant_id => '1', + tenant_id => '2', cdn_id => '2', long_desc => 'test-ds3 long_desc', max_dns_answers => '0', @@ -345,7 +345,7 @@ my %definition_for = ( origin_shield => undef, range_request_handling => '0', remap_text => undef, - tenant_id => '1', + tenant_id => '2', cdn_id => '2', long_desc_2 => 'test long_desc_2', tr_request_headers => undef, @@ -408,7 +408,7 @@ my %definition_for = ( active => '1', cacheurl => undef, ccr_dns_ttl => '3600', - tenant_id => '1', + tenant_id => '2', cdn_id => '2', dns_bypass_ip => '', dns_bypass_ttl => undef, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm index 9f86508daf..991ca2637c 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm @@ -31,7 +31,17 @@ my %definition_for = ( new => 'Tenant', using => { id => 1, + name => 'root-tenant', + last_updated => '2015-12-10 15:43:45', + }, + }, + ## id => 2 + '1' => { + new => 'Tenant', + using => { + id => 2, name => 'tenant_number_1', + parent_id => 1, last_updated => '2015-12-10 15:43:45', }, }, diff --git a/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm b/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm index 7218e459c2..053171fe49 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm @@ -31,7 +31,7 @@ my %definition_for = ( new => 'TmUser', using => { username => 'admin', - tenant_id => '1', + tenant_id => '2', local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', uid => '1', role => '1', @@ -60,7 +60,7 @@ my %definition_for = ( company => undef, confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', username => 'codebig', - tenant_id => '1', + tenant_id => '2', gid => '1', country => 'United States', state_or_province => 'state_or_province', @@ -88,7 +88,7 @@ my %definition_for = ( confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', gid => '1', username => 'extension', - tenant_id => '1', + tenant_id => '2', country => 'United States', email => 'plugin@email.com', state_or_province => 'state_or_province', @@ -114,7 +114,7 @@ my %definition_for = ( confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', company => undef, username => 'migration', - tenant_id => '1', + tenant_id => '2', gid => '1', state_or_province => undef, email => undef, @@ -144,7 +144,7 @@ my %definition_for = ( country => 'United States', gid => '1', username => 'portal', - tenant_id => '1', + tenant_id => '2', confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', company => undef, local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', @@ -170,7 +170,7 @@ my %definition_for = ( state_or_province => 'state_or_province', gid => '1', username => 'testuser', - tenant_id => '1', + tenant_id => '2', last_updated => '2015-12-10 15:43:45', company => undef, confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', diff --git a/traffic_ops/app/lib/Fixtures/Tenant.pm b/traffic_ops/app/lib/Fixtures/Tenant.pm index 7f6a7f26e7..7e0f0cddd0 100644 --- a/traffic_ops/app/lib/Fixtures/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Tenant.pm @@ -20,11 +20,19 @@ use Digest::SHA1 qw(sha1_hex); my %definition_for = ( ## id => 1 - tenant1_tenant_name => { + root_tenant_name => { new => 'Tenant', using => { id => 1, + name => 'root', + }, + }, + tenant1_tenant_name => { + new => 'Tenant', + using => { + id => 2, name => 'tenant1', + parent_id => 1, }, }, ); diff --git a/traffic_ops/app/lib/Fixtures/TmUser.pm b/traffic_ops/app/lib/Fixtures/TmUser.pm index de70ba1900..96b34382a4 100644 --- a/traffic_ops/app/lib/Fixtures/TmUser.pm +++ b/traffic_ops/app/lib/Fixtures/TmUser.pm @@ -27,7 +27,7 @@ my %definition_for = ( using => { id => 100, username => 'admin', - tenant_id => 1, + tenant_id => 2, role => 4, uid => '1', gid => '1', @@ -52,7 +52,7 @@ my %definition_for = ( using => { id => 200, username => 'portal', - tenant_id => 1, + tenant_id => 2, role => 6, uid => '1', gid => '1', @@ -77,7 +77,7 @@ my %definition_for = ( using => { id => 300, username => 'codebig', - tenant_id => 1, + tenant_id => 2, role => 6, uid => '1', gid => '1', @@ -102,7 +102,7 @@ my %definition_for = ( using => { id => 400, username => 'migration', - tenant_id => 1, + tenant_id => 2, role => 5, uid => '1', gid => '1', @@ -127,7 +127,7 @@ my %definition_for = ( using => { id => 500, username => 'federation', - tenant_id => 1, + tenant_id => 2, role => 7, uid => '1', gid => '1', @@ -152,7 +152,7 @@ my %definition_for = ( using => { id => 600, username => 'steering1', - tenant_id => 1, + tenant_id => 2, role => 7, uid => '1', gid => '1', @@ -177,7 +177,7 @@ my %definition_for = ( using => { id => 700, username => 'steering2', - tenant_id => 1, + tenant_id => 2, role => 7, uid => '1', gid => '1', diff --git a/traffic_ops/app/lib/Schema/Result/Cdn.pm b/traffic_ops/app/lib/Schema/Result/Cdn.pm index 55368c9885..fe06d02dd7 100644 --- a/traffic_ops/app/lib/Schema/Result/Cdn.pm +++ b/traffic_ops/app/lib/Schema/Result/Cdn.pm @@ -51,7 +51,7 @@ __PACKAGE__->table("cdn"); =head2 tenant_id data_type: 'bigint' - default_value: 1 + default_value: 2 is_foreign_key: 1 is_nullable: 0 @@ -79,7 +79,7 @@ __PACKAGE__->add_columns( "tenant_id", { data_type => "bigint", - default_value => 1, + default_value => 2, is_foreign_key => 1, is_nullable => 0, }, @@ -174,8 +174,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:58 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:o/4nImKo2c2AAJqoFouQ6g +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 15:01:46 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Uv5I/pvO3MC+ng0e8LeOVA # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm index 7af0f6a80f..78415d5641 100644 --- a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm +++ b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm @@ -291,7 +291,7 @@ __PACKAGE__->table("deliveryservice"); =head2 tenant_id data_type: 'bigint' - default_value: 1 + default_value: 2 is_foreign_key: 1 is_nullable: 0 @@ -409,7 +409,7 @@ __PACKAGE__->add_columns( "tenant_id", { data_type => "bigint", - default_value => 1, + default_value => 2, is_foreign_key => 1, is_nullable => 0, }, @@ -638,8 +638,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:58 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:AVaUXbxO5Efc2Z05PQFINQ +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 15:01:46 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:yYBYZ1tHTl5tO5W2owX6+A # You can replace this text with custom code or comments, and it will be preserved on regeneration # diff --git a/traffic_ops/app/lib/Schema/Result/Tenant.pm b/traffic_ops/app/lib/Schema/Result/Tenant.pm index 80a3821192..c56507016f 100644 --- a/traffic_ops/app/lib/Schema/Result/Tenant.pm +++ b/traffic_ops/app/lib/Schema/Result/Tenant.pm @@ -33,6 +33,12 @@ __PACKAGE__->table("tenant"); data_type: 'text' is_nullable: 0 +=head2 parent_id + + data_type: 'bigint' + is_foreign_key: 1 + is_nullable: 1 + =head2 last_updated data_type: 'timestamp with time zone' @@ -47,6 +53,8 @@ __PACKAGE__->add_columns( { data_type => "bigint", is_nullable => 0 }, "name", { data_type => "text", is_nullable => 0 }, + "parent_id", + { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 }, "last_updated", { data_type => "timestamp with time zone", @@ -114,6 +122,41 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); +=head2 parent + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "parent", + "Schema::Result::Tenant", + { id => "parent_id" }, + { + is_deferrable => 0, + join_type => "LEFT", + on_delete => "NO ACTION", + on_update => "NO ACTION", + }, +); + +=head2 tenants + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "tenants", + "Schema::Result::Tenant", + { "foreign.parent_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 tm_users Type: has_many @@ -130,8 +173,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:59 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:GwD6l7apYu+ouFMo7Jv/Tg +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 14:49:53 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:TqTJ5SqUDIAXj4028Gepfw # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/lib/Schema/Result/TmUser.pm b/traffic_ops/app/lib/Schema/Result/TmUser.pm index d45f6af3da..a8cc6caaad 100644 --- a/traffic_ops/app/lib/Schema/Result/TmUser.pm +++ b/traffic_ops/app/lib/Schema/Result/TmUser.pm @@ -142,7 +142,7 @@ __PACKAGE__->table("tm_user"); =head2 tenant_id data_type: 'bigint' - default_value: 1 + default_value: 2 is_foreign_key: 1 is_nullable: 0 @@ -206,7 +206,7 @@ __PACKAGE__->add_columns( "tenant_id", { data_type => "bigint", - default_value => 1, + default_value => 2, is_foreign_key => 1, is_nullable => 0, }, @@ -348,8 +348,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 09:32:59 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:YyD7RMTRTQfgdo/uLgeezQ +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 15:01:46 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Dbm4DqHCsjYdssGUGmKNPA # You can replace this text with custom code or comments, and it will be preserved on regeneration From 93ed90f9f6bba1e4f3f2c45ce2f2b648bd7507ec Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Sun, 19 Feb 2017 05:19:59 +0200 Subject: [PATCH 03/18] Tenants API --- .../20170221000001_initial_tenancy.sql | 12 +- traffic_ops/app/db/seeds.sql | 4 +- traffic_ops/app/lib/API/Tenant.pm | 230 ++++++++++++++++++ .../app/lib/Fixtures/Integration/Tenant.pm | 1 + traffic_ops/app/lib/Fixtures/Tenant.pm | 1 + traffic_ops/app/lib/Schema/Result/Tenant.pm | 23 +- traffic_ops/app/lib/TrafficOpsRoutes.pm | 8 + traffic_ops/app/t/api/1.2/tenant.t | 80 ++++++ 8 files changed, 349 insertions(+), 10 deletions(-) create mode 100644 traffic_ops/app/lib/API/Tenant.pm create mode 100644 traffic_ops/app/t/api/1.2/tenant.t diff --git a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql index 3d6dc2449a..b6e8492a36 100644 --- a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql +++ b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql @@ -16,14 +16,17 @@ -- +goose Up -- SQL in section 'Up' is executed when this migration is applied + -- tenant CREATE TABLE tenant ( - id bigint primary key NOT NULL, + id BIGSERIAL primary key NOT NULL, name text UNIQUE NOT NULL, - parent_id bigint, - CONSTRAINT fk_parentid FOREIGN KEY (parent_id) REFERENCES tenant(id) ON DELETE CASCADE, + parent_id bigint DEFAULT 1, + CONSTRAINT fk_parentid FOREIGN KEY (parent_id) REFERENCES tenant(id) ON DELETE CASCADE, last_updated timestamp with time zone DEFAULT now() ); +CREATE INDEX idx_k_tenant_parent_tenant_idx ON tenant USING btree (parent_id); + CREATE TRIGGER on_update_current_timestamp BEFORE UPDATE ON tenant FOR EACH ROW EXECUTE PROCEDURE on_update_current_timestamp_last_updated(); @@ -31,16 +34,19 @@ ALTER TABLE tm_user ADD tenant_id BIGINT NOT NULL, ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, ALTER COLUMN tenant_id SET DEFAULT 2; +CREATE INDEX idx_k_tm_user_tenant_idx ON tm_user USING btree (tenant_id); ALTER TABLE cdn ADD tenant_id BIGINT NOT NULL, ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, ALTER COLUMN tenant_id SET DEFAULT 2; +CREATE INDEX idx_k_cdn_tenant_idx ON cdn USING btree (tenant_id); ALTER TABLE deliveryservice ADD tenant_id BIGINT NOT NULL, ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, ALTER COLUMN tenant_id SET DEFAULT 2; +CREATE INDEX idx_k_deliveryservice_tenant_idx ON deliveryservice USING btree (tenant_id); -- +goose Down -- SQL section 'Down' is executed when this migration is rolled back diff --git a/traffic_ops/app/db/seeds.sql b/traffic_ops/app/db/seeds.sql index 189fb189a8..236b83481c 100644 --- a/traffic_ops/app/db/seeds.sql +++ b/traffic_ops/app/db/seeds.sql @@ -149,8 +149,8 @@ insert into profile_parameter (profile, parameter) values ( update server set https_port = 443 where https_port is null; -- tenant -insert into tenant (id, name) values (1, 'root') ON CONFLICT DO NOTHING; -insert into tenant (id, name, parent_id) values (2, 'guest', 1) ON CONFLICT DO NOTHING; +insert into tenant (name, parent_id) values ('root', null) ON CONFLICT DO NOTHING; +insert into tenant (name) values ('guest') ON CONFLICT DO NOTHING; -- users insert into tm_user (username, tenant_id, role,full_name) values ('portal', 2, (select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; diff --git a/traffic_ops/app/lib/API/Tenant.pm b/traffic_ops/app/lib/API/Tenant.pm new file mode 100644 index 0000000000..9cd8861001 --- /dev/null +++ b/traffic_ops/app/lib/API/Tenant.pm @@ -0,0 +1,230 @@ +package API::Tenant; +# +# +# 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. +# +# +# + +use UI::Utils; + +use Mojo::Base 'Mojolicious::Controller'; +use Data::Dumper; +use JSON; +use MojoPlugins::Response; + +my $finfo = __FILE__ . ":"; + +sub getTenantName { + my $self = shift; + my $tenant_id = shift; + return defined($tenant_id) ? $self->db->resultset('Tenant')->search( { id => $tenant_id } )->get_column('name')->single() : "n/a"; +} + +sub isRootTenant { + my $self = shift; + my $tenant_id = shift; + return $tenant_id eq 1; +} + +sub index { + my $self = shift; + my $parent_id = $self->param('parent_iId'); + + my %criteria; + if ( defined $parent_id ) { + $criteria{'parent_id'} = $parent_id; + } + + my @data; + my $orderby = $self->param('orderby') || "name"; + my $rs_data = $self->db->resultset("Tenant")->search( \%criteria, {order_by => 'me.' . $orderby } ); + while ( my $row = $rs_data->next ) { + push( + @data, { + "id" => $row->id, + "name" => $row->name, + "parentId" => $row->parent_id, + #"parentName" => $self->getTenantName($row->parent_id) + } + ); + } + $self->success( \@data ); +} + +sub index_by_name { + my $self = shift; + my $name = $self->param('name'); + + my $rs_data = $self->db->resultset("Tenant")->search( { 'me.name' => $name }); + my @data = (); + while ( my $row = $rs_data->next ) { + push( + @data, { + "id" => $row->id, + "name" => $row->name, + "parentId" => $row->parent_id, + #"parentName" => $self->getTenantName($row->parent_id) + } + ); + } + $self->success( \@data ); +} + +sub show { + my $self = shift; + my $id = $self->param('id'); + + my $rs_data = $self->db->resultset("Tenant")->search( { 'me.id' => $id }); + my @data = (); + while ( my $row = $rs_data->next ) { + push( + @data, { + "id" => $row->id, + "name" => $row->name, + "parentId" => $row->parent_id, + #"parentName" => $self->getTenantName($row->parent_id) + } + ); + } + $self->success( \@data ); +} + +sub update { + my $self = shift; + my $id = $self->param('id'); + my $params = $self->req->json; + + if ( !&is_oper($self) ) { + return $self->forbidden(); + } + + my $tenant = $self->db->resultset('Tenant')->find( { id => $id } ); + if ( !defined($tenant) ) { + return $self->not_found(); + } + + if ( !defined($params) ) { + return $self->alert("Parameters must be in JSON format."); + } + + if ( !defined( $params->{name} ) ) { + return $self->alert("Tenant name is required."); + } + + if ( $params->{name} ne $self->getTenantName($id) ) { + my $name = $params->{name}; + my $existing = $self->db->resultset('Tenant')->search( { name => $name } )->get_column('name')->single(); + if ($existing) { + return $self->alert("A tenant with name \"$name\" already exists."); + } + } + + if ( !defined( $params->{parentId}) && !$self->isRootTenant($id) ) { + return $self->alert("Parent Id is required."); + } + + my $values = { + name => $params->{name}, + parent_id => $params->{parentId} + }; + + my $rs = $tenant->update($values); + if ($rs) { + my $response; + $response->{id} = $rs->id; + $response->{name} = $rs->name; + $response->{parentId} = $rs->parent_id; + #$response->{parentName} = $self->getTenantName($rs->parent_id); + $response->{lastUpdated} = $rs->last_updated; + &log( $self, "Updated Tenant name '" . $rs->name . "' for id: " . $rs->id, "APICHANGE" ); + return $self->success( $response, "Tenant update was successful." ); + } + else { + return $self->alert("Tenant update failed."); + } + +} + + +sub create { + my $self = shift; + my $params = $self->req->json; + + if ( !&is_oper($self) ) { + return $self->forbidden(); + } + + my $name = $params->{name}; + if ( !defined($name) ) { + return $self->alert("Tenant name is required."); + } + + my $parent_id = $params->{parentId}; + if ( !defined($parent_id) ) { + return $self->alert("Parent Id is required."); + } + + my $existing = $self->db->resultset('Tenant')->search( { name => $name } )->get_column('name')->single(); + if ($existing) { + return $self->alert("A tenant with name \"$name\" already exists."); + } + + my $values = { + name => $params->{name} , + parent_id => $params->{parentId} + }; + + my $insert = $self->db->resultset('Tenant')->create($values); + my $rs = $insert->insert(); + if ($rs) { + my $response; + $response->{id} = $rs->id; + $response->{name} = $rs->name; + $response->{parentId} = $rs->parent_id; + #$response->{parentName} = $self->getTenantName($rs->parent_id); + $response->{lastUpdated} = $rs->last_updated; + + &log( $self, "Created Tenant name '" . $rs->name . "' for id: " . $rs->id, "APICHANGE" ); + + return $self->success( $response, "Tenant create was successful." ); + } + else { + return $self->alert("Tenant create failed."); + } + +} + + +sub delete { + my $self = shift; + my $id = $self->param('id'); + + if ( !&is_oper($self) ) { + return $self->forbidden(); + } + + my $tenant = $self->db->resultset('Tenant')->find( { id => $id } ); + if ( !defined($tenant) ) { + return $self->not_found(); + } + + my $rs = $tenant->delete(); + if ($rs) { + return $self->success_message("Tenant deleted."); + } else { + return $self->alert( "Tenant delete failed." ); + } +} + + diff --git a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm index 991ca2637c..60ed0bf1f0 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm @@ -32,6 +32,7 @@ my %definition_for = ( using => { id => 1, name => 'root-tenant', + parent_id => undef, last_updated => '2015-12-10 15:43:45', }, }, diff --git a/traffic_ops/app/lib/Fixtures/Tenant.pm b/traffic_ops/app/lib/Fixtures/Tenant.pm index 7e0f0cddd0..a06156de9b 100644 --- a/traffic_ops/app/lib/Fixtures/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Tenant.pm @@ -25,6 +25,7 @@ my %definition_for = ( using => { id => 1, name => 'root', + parent_id => undef, }, }, tenant1_tenant_name => { diff --git a/traffic_ops/app/lib/Schema/Result/Tenant.pm b/traffic_ops/app/lib/Schema/Result/Tenant.pm index c56507016f..07b7afff08 100644 --- a/traffic_ops/app/lib/Schema/Result/Tenant.pm +++ b/traffic_ops/app/lib/Schema/Result/Tenant.pm @@ -26,7 +26,9 @@ __PACKAGE__->table("tenant"); =head2 id data_type: 'bigint' + is_auto_increment: 1 is_nullable: 0 + sequence: 'tenant_id_seq' =head2 name @@ -36,6 +38,7 @@ __PACKAGE__->table("tenant"); =head2 parent_id data_type: 'bigint' + default_value: 1 is_foreign_key: 1 is_nullable: 1 @@ -50,11 +53,21 @@ __PACKAGE__->table("tenant"); __PACKAGE__->add_columns( "id", - { data_type => "bigint", is_nullable => 0 }, + { + data_type => "bigint", + is_auto_increment => 1, + is_nullable => 0, + sequence => "tenant_id_seq", + }, "name", { data_type => "text", is_nullable => 0 }, "parent_id", - { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 }, + { + data_type => "bigint", + default_value => 1, + is_foreign_key => 1, + is_nullable => 1, + }, "last_updated", { data_type => "timestamp with time zone", @@ -137,7 +150,7 @@ __PACKAGE__->belongs_to( { is_deferrable => 0, join_type => "LEFT", - on_delete => "NO ACTION", + on_delete => "CASCADE", on_update => "NO ACTION", }, ); @@ -173,8 +186,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 14:49:53 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:TqTJ5SqUDIAXj4028Gepfw +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 21:04:12 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:i9zkvG6Tv0q7IYLT5co5qQ # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/lib/TrafficOpsRoutes.pm b/traffic_ops/app/lib/TrafficOpsRoutes.pm index 2d61e58c96..a34a6b9504 100644 --- a/traffic_ops/app/lib/TrafficOpsRoutes.pm +++ b/traffic_ops/app/lib/TrafficOpsRoutes.pm @@ -554,6 +554,14 @@ sub api_routes { # Supports ?orderby=key $r->get("/api/$version/deliveryserviceserver")->over( authenticated => 1 )->to( 'DeliveryServiceServer#index', namespace => $namespace ); + # -- TENANTS + $r->get("/api/$version/tenants")->over( authenticated => 1 )->to( 'Tenant#index', namespace => $namespace ); + $r->get( "/api/$version/tenants/:id" => [ id => qr/\d+/ ] )->over( authenticated => 1 )->to( 'Tenant#show', namespace => $namespace ); + $r->get( "/api/$version/tenants/name/:name")->over( authenticated => 1 )->to( 'Tenant#index_by_name', namespace => $namespace ); + $r->put("/api/$version/tenants/:id")->over( authenticated => 1 )->to( 'Tenant#update', namespace => $namespace ); + $r->post("/api/$version/tenants")->over( authenticated => 1 )->to( 'Tenant#create', namespace => $namespace ); + $r->delete("/api/$version/tenants/:id")->over( authenticated => 1 )->to( 'Tenant#delete', namespace => $namespace ); + # -- DIVISIONS $r->get("/api/$version/divisions")->over( authenticated => 1 )->to( 'Division#index', namespace => $namespace ); $r->get( "/api/$version/divisions/:id" => [ id => qr/\d+/ ] )->over( authenticated => 1 )->to( 'Division#show', namespace => $namespace ); diff --git a/traffic_ops/app/t/api/1.2/tenant.t b/traffic_ops/app/t/api/1.2/tenant.t new file mode 100644 index 0000000000..952c13f521 --- /dev/null +++ b/traffic_ops/app/t/api/1.2/tenant.t @@ -0,0 +1,80 @@ +package main; +# +# +# 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. +# +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use DBI; +use strict; +use warnings; +no warnings 'once'; +use warnings 'all'; +use Test::TestHelper; + +#no_transactions=>1 ==> keep fixtures after every execution, beware of duplicate data! +#no_transactions=>0 ==> delete fixtures after every execution + +BEGIN { $ENV{MOJO_MODE} = "test" } + +my $schema = Schema->connect_to_database; +my $dbh = Schema->database_handle; +my $t = Test::Mojo->new('TrafficOps'); + +Test::TestHelper->unload_core_data($schema); +Test::TestHelper->load_core_data($schema); + +ok $t->post_ok( '/login', => form => { u => Test::TestHelper::ADMIN_USER, p => Test::TestHelper::ADMIN_USER_PASSWORD } )->status_is(302) + ->or( sub { diag $t->tx->res->content->asset->{content}; } ), 'Should login?'; + +$t->get_ok("/api/1.2/tenants")->status_is(200)->json_is( "/response/0/id", 100 ) + ->json_is( "/response/0/name", "mountain" )->or( sub { diag $t->tx->res->content->asset->{content}; } ); + +$t->get_ok("/api/1.2/tenants/100")->status_is(200)->json_is( "/response/0/id", 100 ) + ->json_is( "/response/0/name", "mountain" )->or( sub { diag $t->tx->res->content->asset->{content}; } ); + +ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { + "name" => "tenant1" })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "tenant1" ) + , 'Does the tenant details return?'; + +ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { + "name" => "tenant1" })->status_is(400); + +my $tenant_id = &get_tenant_id('tenant1'); +ok $t->put_ok('/api/1.2/tenants/' . $tenant_id => {Accept => 'application/json'} => json => { + "name" => "tenant2" + }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "tenant2" ) + ->json_is( "/alerts/0/level" => "success" ) + , 'Does the tenant2 details return?'; + +ok $t->delete_ok('/api/1.2/tenants/' . $tenant_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); + +ok $t->get_ok('/logout')->status_is(302)->or( sub { diag $t->tx->res->content->asset->{content}; } ); +$dbh->disconnect(); +done_testing(); + +sub get_tenant_id { + my $name = shift; + my $q = "select id from tenant where name = \'$name\'"; + my $get_svr = $dbh->prepare($q); + $get_svr->execute(); + my $p = $get_svr->fetchall_arrayref( {} ); + $get_svr->finish(); + my $id = $p->[0]->{id}; + return $id; +} + From cd9d032b7c97c2b92c31e4b8c39a0c7c646150e6 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Sun, 19 Feb 2017 07:12:36 +0200 Subject: [PATCH 04/18] identify root tenant by no parent --- traffic_ops/app/lib/API/Tenant.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/traffic_ops/app/lib/API/Tenant.pm b/traffic_ops/app/lib/API/Tenant.pm index 9cd8861001..3581a46345 100644 --- a/traffic_ops/app/lib/API/Tenant.pm +++ b/traffic_ops/app/lib/API/Tenant.pm @@ -34,7 +34,7 @@ sub getTenantName { sub isRootTenant { my $self = shift; my $tenant_id = shift; - return $tenant_id eq 1; + return !defined($self->db->resultset('Tenant')->search( { id => $tenant_id } )->get_column('parent_id')->single()); } sub index { @@ -134,6 +134,10 @@ sub update { return $self->alert("Parent Id is required."); } + if ( !defined($params->{parentId}) && !isRootTenant($id) ) { + return $self->alert("Only the \"root\" tenant can have no parent."); + } + my $values = { name => $params->{name}, parent_id => $params->{parentId} From 8d8516a6c568affb1ac9ed16b0a16fe51fd7d7b2 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Sun, 19 Feb 2017 07:20:07 +0200 Subject: [PATCH 05/18] Verify parent tenant is not the same as id --- .../app/db/migrations/20170221000001_initial_tenancy.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql index b6e8492a36..b00ba84791 100644 --- a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql +++ b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql @@ -21,13 +21,12 @@ CREATE TABLE tenant ( id BIGSERIAL primary key NOT NULL, name text UNIQUE NOT NULL, - parent_id bigint DEFAULT 1, + parent_id bigint DEFAULT 1 CHECK (id != parent_id), CONSTRAINT fk_parentid FOREIGN KEY (parent_id) REFERENCES tenant(id) ON DELETE CASCADE, last_updated timestamp with time zone DEFAULT now() ); CREATE INDEX idx_k_tenant_parent_tenant_idx ON tenant USING btree (parent_id); - CREATE TRIGGER on_update_current_timestamp BEFORE UPDATE ON tenant FOR EACH ROW EXECUTE PROCEDURE on_update_current_timestamp_last_updated(); ALTER TABLE tm_user From 7e14783c733b7a09f27953e27aeab59071e2794c Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Sun, 19 Feb 2017 11:43:15 +0200 Subject: [PATCH 06/18] defulat tenant is null --- .../20170221000001_initial_tenancy.sql | 12 ++++----- traffic_ops/app/db/seeds.sql | 1 - traffic_ops/app/lib/Fixtures/Cdn.pm | 4 +-- .../app/lib/Fixtures/Deliveryservice.pm | 26 +++++++++---------- .../app/lib/Fixtures/Integration/Cdn.pm | 4 +-- .../Fixtures/Integration/Deliveryservice.pm | 16 ++++++------ .../app/lib/Fixtures/Integration/Tenant.pm | 14 ++-------- .../app/lib/Fixtures/Integration/TmUser.pm | 12 ++++----- traffic_ops/app/lib/Fixtures/Tenant.pm | 10 +------ traffic_ops/app/lib/Fixtures/TmUser.pm | 14 +++++----- traffic_ops/app/lib/Schema/Result/Cdn.pm | 21 +++++++-------- .../app/lib/Schema/Result/Deliveryservice.pm | 21 +++++++-------- traffic_ops/app/lib/Schema/Result/TmUser.pm | 21 +++++++-------- 13 files changed, 77 insertions(+), 99 deletions(-) diff --git a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql index b00ba84791..27efbdec57 100644 --- a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql +++ b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql @@ -30,21 +30,21 @@ CREATE INDEX idx_k_tenant_parent_tenant_idx ON tenant USING btree (parent_id); CREATE TRIGGER on_update_current_timestamp BEFORE UPDATE ON tenant FOR EACH ROW EXECUTE PROCEDURE on_update_current_timestamp_last_updated(); ALTER TABLE tm_user - ADD tenant_id BIGINT NOT NULL, + ADD tenant_id BIGINT, ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, - ALTER COLUMN tenant_id SET DEFAULT 2; + ALTER COLUMN tenant_id SET DEFAULT NULL; CREATE INDEX idx_k_tm_user_tenant_idx ON tm_user USING btree (tenant_id); ALTER TABLE cdn - ADD tenant_id BIGINT NOT NULL, + ADD tenant_id BIGINT, ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, - ALTER COLUMN tenant_id SET DEFAULT 2; + ALTER COLUMN tenant_id SET DEFAULT NULL; CREATE INDEX idx_k_cdn_tenant_idx ON cdn USING btree (tenant_id); ALTER TABLE deliveryservice - ADD tenant_id BIGINT NOT NULL, + ADD tenant_id BIGINT, ADD CONSTRAINT fk_tenantid FOREIGN KEY (tenant_id) REFERENCES tenant (id) MATCH FULL, - ALTER COLUMN tenant_id SET DEFAULT 2; + ALTER COLUMN tenant_id SET DEFAULT NULL; CREATE INDEX idx_k_deliveryservice_tenant_idx ON deliveryservice USING btree (tenant_id); -- +goose Down diff --git a/traffic_ops/app/db/seeds.sql b/traffic_ops/app/db/seeds.sql index 236b83481c..41585d29f3 100644 --- a/traffic_ops/app/db/seeds.sql +++ b/traffic_ops/app/db/seeds.sql @@ -150,7 +150,6 @@ update server set https_port = 443 where https_port is null; -- tenant insert into tenant (name, parent_id) values ('root', null) ON CONFLICT DO NOTHING; -insert into tenant (name) values ('guest') ON CONFLICT DO NOTHING; -- users insert into tm_user (username, tenant_id, role,full_name) values ('portal', 2, (select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; diff --git a/traffic_ops/app/lib/Fixtures/Cdn.pm b/traffic_ops/app/lib/Fixtures/Cdn.pm index f658544425..781a0f1e6c 100644 --- a/traffic_ops/app/lib/Fixtures/Cdn.pm +++ b/traffic_ops/app/lib/Fixtures/Cdn.pm @@ -25,7 +25,7 @@ my %definition_for = ( using => { id => 100, name => 'cdn1', - tenant_id => 2, + tenant_id => undef, }, }, ## id => 2 @@ -34,7 +34,7 @@ my %definition_for = ( using => { id => 200, name => 'cdn2', - tenant_id => 2, + tenant_id => undef, }, }, ); diff --git a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm index 57f7466c55..0f91ede771 100644 --- a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm +++ b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm @@ -25,7 +25,7 @@ my %definition_for = ( id => 100, xml_id => 'test-ds1', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -64,7 +64,7 @@ my %definition_for = ( id => 200, xml_id => 'test-ds2', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -103,7 +103,7 @@ my %definition_for = ( id => 300, xml_id => 'test-ds3', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -142,7 +142,7 @@ my %definition_for = ( id => 400, xml_id => 'test-ds4', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -181,7 +181,7 @@ my %definition_for = ( id => 500, xml_id => 'test-ds5', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -220,7 +220,7 @@ my %definition_for = ( id => 600, xml_id => 'test-ds6', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -259,7 +259,7 @@ my %definition_for = ( id => 700, xml_id => 'steering-ds1', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -296,7 +296,7 @@ my %definition_for = ( id => 800, xml_id => 'steering-ds2', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -333,7 +333,7 @@ my %definition_for = ( id => 900, xml_id => 'steering-ds3', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -370,7 +370,7 @@ my %definition_for = ( id => 1000, xml_id => 'steering-target-ds1', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -407,7 +407,7 @@ my %definition_for = ( id => 1100, xml_id => 'steering-target-ds2', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -444,7 +444,7 @@ my %definition_for = ( id => 1200, xml_id => 'steering-target-ds3', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, @@ -481,7 +481,7 @@ my %definition_for = ( id => 1300, xml_id => 'steering-target-ds4', active => 1, - tenant_id => 2, + tenant_id => undef, dscp => 40, signed => 0, qstring_ignore => 0, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm b/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm index 4480ddd420..6c1cdbfafc 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Cdn.pm @@ -31,7 +31,7 @@ my %definition_for = ( new => 'Cdn', using => { name => 'cdn_number_1', - tenant_id => '2', + tenant_id => undef, dnssec_enabled => '0', last_updated => '2015-12-10 15:43:45', }, @@ -41,7 +41,7 @@ my %definition_for = ( new => 'Cdn', using => { name => 'cdn_number_2', - tenant_id => '2', + tenant_id => undef, dnssec_enabled => '0', last_updated => '2015-12-10 15:43:45', }, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm index 1c729fb820..34e2171cee 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm @@ -63,7 +63,7 @@ my %definition_for = ( global_max_tps => '0', max_dns_answers => '0', tr_response_headers => undef, - tenant_id => '2', + tenant_id => undef, cdn_id => '2', dns_bypass_ttl => undef, initial_dispersion => '1', @@ -102,7 +102,7 @@ my %definition_for = ( type => '10', ipv6_routing_enabled => undef, tr_response_headers => undef, - tenant_id => '2', + tenant_id => undef, cdn_id => '1', global_max_mbps => '0', global_max_tps => '0', @@ -172,7 +172,7 @@ my %definition_for = ( remap_text => undef, ssl_key_version => '0', tr_request_headers => undef, - tenant_id => '2', + tenant_id => undef, cdn_id => '1', global_max_tps => '0', http_bypass_fqdn => '', @@ -198,7 +198,7 @@ my %definition_for = ( last_updated => '2015-12-10 15:44:37', signed => '0', ccr_dns_ttl => '3600', - tenant_id => '2', + tenant_id => undef, cdn_id => '1', display_name => 'movies-c1', protocol => '0', @@ -277,7 +277,7 @@ my %definition_for = ( global_max_tps => '0', http_bypass_fqdn => '', origin_shield => undef, - tenant_id => '2', + tenant_id => undef, cdn_id => '1', dns_bypass_ttl => undef, }, @@ -296,7 +296,7 @@ my %definition_for = ( mid_header_rewrite => undef, signed => '0', ssl_key_version => '0', - tenant_id => '2', + tenant_id => undef, cdn_id => '2', long_desc => 'test-ds3 long_desc', max_dns_answers => '0', @@ -345,7 +345,7 @@ my %definition_for = ( origin_shield => undef, range_request_handling => '0', remap_text => undef, - tenant_id => '2', + tenant_id => undef, cdn_id => '2', long_desc_2 => 'test long_desc_2', tr_request_headers => undef, @@ -408,7 +408,7 @@ my %definition_for = ( active => '1', cacheurl => undef, ccr_dns_ttl => '3600', - tenant_id => '2', + tenant_id => undef, cdn_id => '2', dns_bypass_ip => '', dns_bypass_ttl => undef, diff --git a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm index 60ed0bf1f0..a4c28e31e5 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm @@ -30,22 +30,12 @@ my %definition_for = ( '0' => { new => 'Tenant', using => { - id => 1, - name => 'root-tenant', + #id => 1,#not setting the id in order not to confuse the id sequence + name => 'root', parent_id => undef, last_updated => '2015-12-10 15:43:45', }, }, - ## id => 2 - '1' => { - new => 'Tenant', - using => { - id => 2, - name => 'tenant_number_1', - parent_id => 1, - last_updated => '2015-12-10 15:43:45', - }, - }, ); sub name { diff --git a/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm b/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm index 053171fe49..37e3db62dc 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/TmUser.pm @@ -31,7 +31,7 @@ my %definition_for = ( new => 'TmUser', using => { username => 'admin', - tenant_id => '2', + tenant_id => undef, local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', uid => '1', role => '1', @@ -60,7 +60,7 @@ my %definition_for = ( company => undef, confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', username => 'codebig', - tenant_id => '2', + tenant_id => undef, gid => '1', country => 'United States', state_or_province => 'state_or_province', @@ -88,7 +88,7 @@ my %definition_for = ( confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', gid => '1', username => 'extension', - tenant_id => '2', + tenant_id => undef, country => 'United States', email => 'plugin@email.com', state_or_province => 'state_or_province', @@ -114,7 +114,7 @@ my %definition_for = ( confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', company => undef, username => 'migration', - tenant_id => '2', + tenant_id => undef, gid => '1', state_or_province => undef, email => undef, @@ -144,7 +144,7 @@ my %definition_for = ( country => 'United States', gid => '1', username => 'portal', - tenant_id => '2', + tenant_id => undef, confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', company => undef, local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', @@ -170,7 +170,7 @@ my %definition_for = ( state_or_province => 'state_or_province', gid => '1', username => 'testuser', - tenant_id => '2', + tenant_id => undef, last_updated => '2015-12-10 15:43:45', company => undef, confirm_local_passwd => '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', diff --git a/traffic_ops/app/lib/Fixtures/Tenant.pm b/traffic_ops/app/lib/Fixtures/Tenant.pm index a06156de9b..615d81177d 100644 --- a/traffic_ops/app/lib/Fixtures/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Tenant.pm @@ -23,19 +23,11 @@ my %definition_for = ( root_tenant_name => { new => 'Tenant', using => { - id => 1, + #id => 1, #not setting the id in order not to confuse the id sequence name => 'root', parent_id => undef, }, }, - tenant1_tenant_name => { - new => 'Tenant', - using => { - id => 2, - name => 'tenant1', - parent_id => 1, - }, - }, ); sub get_definition { diff --git a/traffic_ops/app/lib/Fixtures/TmUser.pm b/traffic_ops/app/lib/Fixtures/TmUser.pm index 96b34382a4..2ed78babcd 100644 --- a/traffic_ops/app/lib/Fixtures/TmUser.pm +++ b/traffic_ops/app/lib/Fixtures/TmUser.pm @@ -27,7 +27,7 @@ my %definition_for = ( using => { id => 100, username => 'admin', - tenant_id => 2, + tenant_id => undef, role => 4, uid => '1', gid => '1', @@ -52,7 +52,7 @@ my %definition_for = ( using => { id => 200, username => 'portal', - tenant_id => 2, + tenant_id => undef, role => 6, uid => '1', gid => '1', @@ -77,7 +77,7 @@ my %definition_for = ( using => { id => 300, username => 'codebig', - tenant_id => 2, + tenant_id => undef, role => 6, uid => '1', gid => '1', @@ -102,7 +102,7 @@ my %definition_for = ( using => { id => 400, username => 'migration', - tenant_id => 2, + tenant_id => undef, role => 5, uid => '1', gid => '1', @@ -127,7 +127,7 @@ my %definition_for = ( using => { id => 500, username => 'federation', - tenant_id => 2, + tenant_id => undef, role => 7, uid => '1', gid => '1', @@ -152,7 +152,7 @@ my %definition_for = ( using => { id => 600, username => 'steering1', - tenant_id => 2, + tenant_id => undef, role => 7, uid => '1', gid => '1', @@ -177,7 +177,7 @@ my %definition_for = ( using => { id => 700, username => 'steering2', - tenant_id => 2, + tenant_id => undef, role => 7, uid => '1', gid => '1', diff --git a/traffic_ops/app/lib/Schema/Result/Cdn.pm b/traffic_ops/app/lib/Schema/Result/Cdn.pm index fe06d02dd7..982262a4fd 100644 --- a/traffic_ops/app/lib/Schema/Result/Cdn.pm +++ b/traffic_ops/app/lib/Schema/Result/Cdn.pm @@ -51,9 +51,8 @@ __PACKAGE__->table("cdn"); =head2 tenant_id data_type: 'bigint' - default_value: 2 is_foreign_key: 1 - is_nullable: 0 + is_nullable: 1 =cut @@ -77,12 +76,7 @@ __PACKAGE__->add_columns( "dnssec_enabled", { data_type => "boolean", default_value => \"false", is_nullable => 0 }, "tenant_id", - { - data_type => "bigint", - default_value => 2, - is_foreign_key => 1, - is_nullable => 0, - }, + { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -170,12 +164,17 @@ __PACKAGE__->belongs_to( "tenant", "Schema::Result::Tenant", { id => "tenant_id" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, + { + is_deferrable => 0, + join_type => "LEFT", + on_delete => "NO ACTION", + on_update => "NO ACTION", + }, ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 15:01:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Uv5I/pvO3MC+ng0e8LeOVA +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-19 10:20:47 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:MMmxuT6/8TCFkUmJdYeoVQ # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm index 78415d5641..aee4a7b210 100644 --- a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm +++ b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm @@ -291,9 +291,8 @@ __PACKAGE__->table("deliveryservice"); =head2 tenant_id data_type: 'bigint' - default_value: 2 is_foreign_key: 1 - is_nullable: 0 + is_nullable: 1 =cut @@ -407,12 +406,7 @@ __PACKAGE__->add_columns( "geolimit_redirect_url", { data_type => "text", is_nullable => 1 }, "tenant_id", - { - data_type => "bigint", - default_value => 2, - is_foreign_key => 1, - is_nullable => 0, - }, + { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -619,7 +613,12 @@ __PACKAGE__->belongs_to( "tenant", "Schema::Result::Tenant", { id => "tenant_id" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, + { + is_deferrable => 0, + join_type => "LEFT", + on_delete => "NO ACTION", + on_update => "NO ACTION", + }, ); =head2 type @@ -638,8 +637,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 15:01:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:yYBYZ1tHTl5tO5W2owX6+A +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-19 10:20:47 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:REpODBN1VhP+vyfl49Jt7g # You can replace this text with custom code or comments, and it will be preserved on regeneration # diff --git a/traffic_ops/app/lib/Schema/Result/TmUser.pm b/traffic_ops/app/lib/Schema/Result/TmUser.pm index a8cc6caaad..91d57af45a 100644 --- a/traffic_ops/app/lib/Schema/Result/TmUser.pm +++ b/traffic_ops/app/lib/Schema/Result/TmUser.pm @@ -142,9 +142,8 @@ __PACKAGE__->table("tm_user"); =head2 tenant_id data_type: 'bigint' - default_value: 2 is_foreign_key: 1 - is_nullable: 0 + is_nullable: 1 =cut @@ -204,12 +203,7 @@ __PACKAGE__->add_columns( "registration_sent", { data_type => "timestamp with time zone", is_nullable => 1 }, "tenant_id", - { - data_type => "bigint", - default_value => 2, - is_foreign_key => 1, - is_nullable => 0, - }, + { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -344,12 +338,17 @@ __PACKAGE__->belongs_to( "tenant", "Schema::Result::Tenant", { id => "tenant_id" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, + { + is_deferrable => 0, + join_type => "LEFT", + on_delete => "NO ACTION", + on_update => "NO ACTION", + }, ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 15:01:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Dbm4DqHCsjYdssGUGmKNPA +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-19 10:20:47 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:2lI3iG0t7INKH+xQq+lo9g # You can replace this text with custom code or comments, and it will be preserved on regeneration From 16a600ddf7c7282e44ee83170b5f3111c656a8b8 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Sun, 19 Feb 2017 13:27:00 +0200 Subject: [PATCH 07/18] api/1.2/tenant.t ut --- .../20170221000001_initial_tenancy.sql | 2 +- traffic_ops/app/t/api/1.2/tenant.t | 54 ++++++++++++++----- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql index 27efbdec57..d708165a8f 100644 --- a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql +++ b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql @@ -22,7 +22,7 @@ CREATE TABLE tenant ( id BIGSERIAL primary key NOT NULL, name text UNIQUE NOT NULL, parent_id bigint DEFAULT 1 CHECK (id != parent_id), - CONSTRAINT fk_parentid FOREIGN KEY (parent_id) REFERENCES tenant(id) ON DELETE CASCADE, + CONSTRAINT fk_parentid FOREIGN KEY (parent_id) REFERENCES tenant(id), last_updated timestamp with time zone DEFAULT now() ); CREATE INDEX idx_k_tenant_parent_tenant_idx ON tenant USING btree (parent_id); diff --git a/traffic_ops/app/t/api/1.2/tenant.t b/traffic_ops/app/t/api/1.2/tenant.t index 952c13f521..2fa96ae166 100644 --- a/traffic_ops/app/t/api/1.2/tenant.t +++ b/traffic_ops/app/t/api/1.2/tenant.t @@ -38,30 +38,60 @@ Test::TestHelper->load_core_data($schema); ok $t->post_ok( '/login', => form => { u => Test::TestHelper::ADMIN_USER, p => Test::TestHelper::ADMIN_USER_PASSWORD } )->status_is(302) ->or( sub { diag $t->tx->res->content->asset->{content}; } ), 'Should login?'; -$t->get_ok("/api/1.2/tenants")->status_is(200)->json_is( "/response/0/id", 100 ) - ->json_is( "/response/0/name", "mountain" )->or( sub { diag $t->tx->res->content->asset->{content}; } ); +#verifying the basic cfg +$t->get_ok("/api/1.2/tenants")->status_is(200)->json_is( "/response/0/name", "root" )->or( sub { diag $t->tx->res->content->asset->{content}; } );; -$t->get_ok("/api/1.2/tenants/100")->status_is(200)->json_is( "/response/0/id", 100 ) - ->json_is( "/response/0/name", "mountain" )->or( sub { diag $t->tx->res->content->asset->{content}; } ); +my $root_tenant_id = &get_tenant_id('root'); ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { - "name" => "tenant1" })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/name" => "tenant1" ) + "name" => "tenantA", "parentId" => $root_tenant_id })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "tenantA" ) + ->json_is( "/response/parentId" => $root_tenant_id) , 'Does the tenant details return?'; +#same name - would not accept ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { - "name" => "tenant1" })->status_is(400); + "name" => "tenantA", "parentId" => $root_tenant_id })->status_is(400); -my $tenant_id = &get_tenant_id('tenant1'); -ok $t->put_ok('/api/1.2/tenants/' . $tenant_id => {Accept => 'application/json'} => json => { - "name" => "tenant2" +#no name - would not accept +ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { + "parentId" => $root_tenant_id })->status_is(400); + +#no parent - would not accept +ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { + "name" => "tenantB" })->status_is(400); + +#rename +my $tenantA_id = &get_tenant_id('tenantA'); +ok $t->put_ok('/api/1.2/tenants/' . $tenantA_id => {Accept => 'application/json'} => json => { + "name" => "tenantA2", "parentId" => $root_tenant_id }) ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/name" => "tenant2" ) + ->json_is( "/response/name" => "tenantA2" ) + ->json_is( "/response/id" => $tenantA_id ) + ->json_is( "/response/parentId" => $root_tenant_id ) ->json_is( "/alerts/0/level" => "success" ) , 'Does the tenant2 details return?'; -ok $t->delete_ok('/api/1.2/tenants/' . $tenant_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); +#cannot change tenant parent to undef +ok $t->put_ok('/api/1.2/tenants/' . $tenantA_id => {Accept => 'application/json'} => json => { + "name" => "tenantC", + })->status_is(400); + +#adding a child tenant +ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { + "name" => "tenantD", "parentId" => $tenantA_id })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "tenantD" ) + ->json_is( "/response/parentId" => $tenantA_id) + , 'Does the tenant details return?'; + +#cannot delete a tenant that have children +ok $t->delete_ok('/api/1.2/tenants/' . $tenantA_id)->status_is(500); + +my $tenantD_id = &get_tenant_id('tenantD'); + +ok $t->delete_ok('/api/1.2/tenants/' . $tenantD_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); +ok $t->delete_ok('/api/1.2/tenants/' . $tenantA_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); ok $t->get_ok('/logout')->status_is(302)->or( sub { diag $t->tx->res->content->asset->{content}; } ); $dbh->disconnect(); From 15c5fceb864e4f252277bee4381d86a8915cb228 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 00:23:27 +0200 Subject: [PATCH 08/18] Organizing initial tenancy setting for first install/upgrade --- traffic_ops/app/db/seeds.sql | 8 +- .../app/db/upgrades/2.2/tenancy/upgrade | 122 ++++++++++++++++++ 2 files changed, 127 insertions(+), 3 deletions(-) create mode 100755 traffic_ops/app/db/upgrades/2.2/tenancy/upgrade diff --git a/traffic_ops/app/db/seeds.sql b/traffic_ops/app/db/seeds.sql index 41585d29f3..66442e961e 100644 --- a/traffic_ops/app/db/seeds.sql +++ b/traffic_ops/app/db/seeds.sql @@ -148,12 +148,14 @@ insert into profile_parameter (profile, parameter) values ( ) ON CONFLICT DO NOTHING; update server set https_port = 443 where https_port is null; --- tenant +-- root tenant insert into tenant (name, parent_id) values ('root', null) ON CONFLICT DO NOTHING; -- users -insert into tm_user (username, tenant_id, role,full_name) values ('portal', 2, (select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; -insert into tm_user (username, tenant_id, role, full_name, token) values ('extension', 2, 3, 'Extension User, DO NOT DELETE', '91504CE6-8E4A-46B2-9F9F-FE7C15228498') ON CONFLICT DO NOTHING; +insert into tm_user (username, role, full_name) values ('portal', (select id from role where name='portal'),'Portal User') ON CONFLICT DO NOTHING; +insert into tm_user (username, role, full_name, token) values ('extension', 3, 'Extension User, DO NOT DELETE', '91504CE6-8E4A-46B2-9F9F-FE7C15228498') ON CONFLICT DO NOTHING; +insert into tm_user (username, tenant_id, role, full_name) values ('admin-root', 1, 4, 'Admin of the "root" tenancy') ON CONFLICT DO NOTHING; + -- to extensions -- some of the old ones do not get a new place, and there will be 'gaps' in the column usage.... New to_extension add will have to take care of that. diff --git a/traffic_ops/app/db/upgrades/2.2/tenancy/upgrade b/traffic_ops/app/db/upgrades/2.2/tenancy/upgrade new file mode 100755 index 0000000000..e6e1c4bb07 --- /dev/null +++ b/traffic_ops/app/db/upgrades/2.2/tenancy/upgrade @@ -0,0 +1,122 @@ +#!/usr/bin/perl + +package main; +# +# +# 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. +# + +use Getopt::Long; +use DBI; +use Schema; + +use YAML; +use YAML qw(LoadFile); +use DBIx::Class::Schema::Loader qw/make_schema_at/; + +BEGIN { $ENV{MOJO_MODE} = "test" } + +use DBIx::Class::Schema::Loader qw/make_schema_at/; + +#TEMPLATE TO BE CHANGED BY THE USER +################################################################################################# +# Environment: If not set by command-line, the below variable setting will take effect. +# If variable is not set, we are counting on the MOJO_MODE env variable alternatively may +my $default_environment = ''; + +# The tenant user to be created: If not set by command-line, the below variable setting will take effect. +# There must not be any tenant already defined +my $default_tenant_name = 'root'; + +# The admin user name to be set with this tenancy: If not set by command-line, the below variable setting will take effect. +# This user must be already defined and have no tenancy +my $default_admin_user_name = 'admin'; +#################################################################################################3 +#DONE TEMPLATE TO BE CHANGED BY THE USER + + + +#parameters retrival +my $environment = ''; +my $tenant_name = ''; +my $admin_user_name = ''; + +GetOptions( "env:s", \$environment, "tenant-name:s", \$tenant_name, "admin-user-name:s", \$admin_user_name ); + + +if ($environment eq "") { + $environment = $default_environment; +} +if ($environment eq "") { + $environment = $ENV{'MOJO_MODE'}; +} +if ($environment eq "") { + print "No environment set!\n" and exit(1); +} +$ENV{'MOJO_MODE'} = $environment; + + +if ($tenant_name eq "") { + $tenant_name = $default_tenant_name; +} +if ($tenant_name eq "") { + print "No tenant-name set!\n" and exit(1); +} + + +if ($admin_user_name eq "") { + $admin_user_name = $default_admin_user_name; +} +if ($admin_user_name eq "") { + print "No admin-user-name set!\n" and exit(1); +} + +#DB connection +my $dbh = Schema->database_handle; +my $schema = Schema->connect_to_database; + + +#validity checks +my $already_set_tenants = $schema->resultset('Tenant')->search( undef ); +if (defined($already_set_tenants)){ + print "System already have tenants!\n" and exit(1); +} + +my $user_data = $schema->resultset('TmUser')->search({ username => $admin_user_name }, { prefetch => [ 'role' ]})->single; +if (!defined($user_data)) { + print "User $admin_user_name does not exists!\n" and exit(1); +} + +if ( $user_data->role->name ne "admin" ) { + print "User $admin_user_name role is not 'admin' ($user_data->role->name)!\n" and exit(1); +} + + +#actual work +my $tenant_values = { + name => $tenant_name, + parent_id => undef + }; + +my $insert = $schema->resultset('Tenant')->create($tenant_values)->insert(); +if (! $insert) { + print "Failed creating tenant!\n" and exit; +} + +my $user = $schema->resultset('TmUser')->find( { id => $user_data->id } ); +my $rc = $user->update({tenant_id=>$insert->id}); + +if (! $rc) { + print "Failed setting tenancy to user!\n" and exit(1); +} +exit(0); From 2100b31b5989cb141debf5465526c26df9a7798d Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 02:57:59 +0200 Subject: [PATCH 09/18] Add tenant is-active flag --- .../20170221000001_initial_tenancy.sql | 3 +- traffic_ops/app/lib/API/Tenant.pm | 29 +++++++--- .../app/lib/Fixtures/Integration/Tenant.pm | 3 +- traffic_ops/app/lib/Fixtures/Tenant.pm | 3 +- traffic_ops/app/lib/Schema/Result/Tenant.pm | 14 ++++- traffic_ops/app/t/api/1.2/tenant.t | 56 +++++++++++++++++-- 6 files changed, 90 insertions(+), 18 deletions(-) diff --git a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql index d708165a8f..7c84615dc7 100644 --- a/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql +++ b/traffic_ops/app/db/migrations/20170221000001_initial_tenancy.sql @@ -21,10 +21,11 @@ CREATE TABLE tenant ( id BIGSERIAL primary key NOT NULL, name text UNIQUE NOT NULL, + active boolean NOT NULL DEFAULT false, parent_id bigint DEFAULT 1 CHECK (id != parent_id), CONSTRAINT fk_parentid FOREIGN KEY (parent_id) REFERENCES tenant(id), last_updated timestamp with time zone DEFAULT now() -); +); CREATE INDEX idx_k_tenant_parent_tenant_idx ON tenant USING btree (parent_id); CREATE TRIGGER on_update_current_timestamp BEFORE UPDATE ON tenant FOR EACH ROW EXECUTE PROCEDURE on_update_current_timestamp_last_updated(); diff --git a/traffic_ops/app/lib/API/Tenant.pm b/traffic_ops/app/lib/API/Tenant.pm index 3581a46345..d5d5ea234a 100644 --- a/traffic_ops/app/lib/API/Tenant.pm +++ b/traffic_ops/app/lib/API/Tenant.pm @@ -39,21 +39,16 @@ sub isRootTenant { sub index { my $self = shift; - my $parent_id = $self->param('parent_iId'); - - my %criteria; - if ( defined $parent_id ) { - $criteria{'parent_id'} = $parent_id; - } my @data; my $orderby = $self->param('orderby') || "name"; - my $rs_data = $self->db->resultset("Tenant")->search( \%criteria, {order_by => 'me.' . $orderby } ); + my $rs_data = $self->db->resultset("Tenant")->search( undef, {order_by => 'me.' . $orderby } ); while ( my $row = $rs_data->next ) { push( @data, { "id" => $row->id, "name" => $row->name, + "active" => $row->active, "parentId" => $row->parent_id, #"parentName" => $self->getTenantName($row->parent_id) } @@ -62,6 +57,7 @@ sub index { $self->success( \@data ); } + sub index_by_name { my $self = shift; my $name = $self->param('name'); @@ -73,6 +69,7 @@ sub index_by_name { @data, { "id" => $row->id, "name" => $row->name, + "active" => $row->active, "parentId" => $row->parent_id, #"parentName" => $self->getTenantName($row->parent_id) } @@ -92,6 +89,7 @@ sub show { @data, { "id" => $row->id, "name" => $row->name, + "active" => $row->active, "parentId" => $row->parent_id, #"parentName" => $self->getTenantName($row->parent_id) } @@ -133,6 +131,13 @@ sub update { if ( !defined( $params->{parentId}) && !$self->isRootTenant($id) ) { return $self->alert("Parent Id is required."); } + + my $is_active = $params->{active}; + + if ( !$params->{active} && $self->isRootTenant($id)) { + return $self->alert("Root user cannot be in-active."); + } + if ( !defined($params->{parentId}) && !isRootTenant($id) ) { return $self->alert("Only the \"root\" tenant can have no parent."); @@ -140,6 +145,7 @@ sub update { my $values = { name => $params->{name}, + active => $params->{active}, parent_id => $params->{parentId} }; @@ -148,6 +154,7 @@ sub update { my $response; $response->{id} = $rs->id; $response->{name} = $rs->name; + $response->{active} = $rs->active; $response->{parentId} = $rs->parent_id; #$response->{parentName} = $self->getTenantName($rs->parent_id); $response->{lastUpdated} = $rs->last_updated; @@ -184,8 +191,15 @@ sub create { return $self->alert("A tenant with name \"$name\" already exists."); } + my $is_active = exists($params->{active})? $params->{active} : 0; #optional, if not set use default + + if ( !$is_active && !defined($parent_id)) { + return $self->alert("Root user cannot be in-active."); + } + my $values = { name => $params->{name} , + active => $is_active, parent_id => $params->{parentId} }; @@ -195,6 +209,7 @@ sub create { my $response; $response->{id} = $rs->id; $response->{name} = $rs->name; + $response->{active} = $rs->active; $response->{parentId} = $rs->parent_id; #$response->{parentName} = $self->getTenantName($rs->parent_id); $response->{lastUpdated} = $rs->last_updated; diff --git a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm index a4c28e31e5..0d3e721d6f 100644 --- a/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Integration/Tenant.pm @@ -30,8 +30,9 @@ my %definition_for = ( '0' => { new => 'Tenant', using => { - #id => 1,#not setting the id in order not to confuse the id sequence + #id => 10**9,#a large in order not to confuse the id sequence name => 'root', + active => 1, parent_id => undef, last_updated => '2015-12-10 15:43:45', }, diff --git a/traffic_ops/app/lib/Fixtures/Tenant.pm b/traffic_ops/app/lib/Fixtures/Tenant.pm index 615d81177d..e0c871a654 100644 --- a/traffic_ops/app/lib/Fixtures/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Tenant.pm @@ -23,8 +23,9 @@ my %definition_for = ( root_tenant_name => { new => 'Tenant', using => { - #id => 1, #not setting the id in order not to confuse the id sequence + #id => 10**9, #a large number not to confuse the id sequence name => 'root', + active => 1, parent_id => undef, }, }, diff --git a/traffic_ops/app/lib/Schema/Result/Tenant.pm b/traffic_ops/app/lib/Schema/Result/Tenant.pm index 07b7afff08..729d208fc0 100644 --- a/traffic_ops/app/lib/Schema/Result/Tenant.pm +++ b/traffic_ops/app/lib/Schema/Result/Tenant.pm @@ -35,6 +35,12 @@ __PACKAGE__->table("tenant"); data_type: 'text' is_nullable: 0 +=head2 active + + data_type: 'boolean' + default_value: false + is_nullable: 0 + =head2 parent_id data_type: 'bigint' @@ -61,6 +67,8 @@ __PACKAGE__->add_columns( }, "name", { data_type => "text", is_nullable => 0 }, + "active", + { data_type => "boolean", default_value => \"false", is_nullable => 0 }, "parent_id", { data_type => "bigint", @@ -150,7 +158,7 @@ __PACKAGE__->belongs_to( { is_deferrable => 0, join_type => "LEFT", - on_delete => "CASCADE", + on_delete => "NO ACTION", on_update => "NO ACTION", }, ); @@ -186,8 +194,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-18 21:04:12 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:i9zkvG6Tv0q7IYLT5co5qQ +# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-02-28 01:59:42 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Qg/su0fUzS2zjFtZHD1hNA # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/traffic_ops/app/t/api/1.2/tenant.t b/traffic_ops/app/t/api/1.2/tenant.t index 2fa96ae166..22416719b1 100644 --- a/traffic_ops/app/t/api/1.2/tenant.t +++ b/traffic_ops/app/t/api/1.2/tenant.t @@ -32,6 +32,9 @@ my $schema = Schema->connect_to_database; my $dbh = Schema->database_handle; my $t = Test::Mojo->new('TrafficOps'); +my $false = 0; +my $true = 1; + Test::TestHelper->unload_core_data($schema); Test::TestHelper->load_core_data($schema); @@ -43,15 +46,17 @@ $t->get_ok("/api/1.2/tenants")->status_is(200)->json_is( "/response/0/name", "ro my $root_tenant_id = &get_tenant_id('root'); +#setting with no "active" field which is optional ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { "name" => "tenantA", "parentId" => $root_tenant_id })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) ->json_is( "/response/name" => "tenantA" ) + ->json_is( "/response/active" => $false) ->json_is( "/response/parentId" => $root_tenant_id) , 'Does the tenant details return?'; #same name - would not accept ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { - "name" => "tenantA", "parentId" => $root_tenant_id })->status_is(400); + "name" => "tenantA", "active" => $true, "parentId" => $root_tenant_id })->status_is(400); #no name - would not accept ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { @@ -61,27 +66,66 @@ ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { "name" => "tenantB" })->status_is(400); -#rename my $tenantA_id = &get_tenant_id('tenantA'); +#rename, and move to active +ok $t->put_ok('/api/1.2/tenants/' . $tenantA_id => {Accept => 'application/json'} => json => { + "name" => "tenantA2", "active" => $true, "parentId" => $root_tenant_id + }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "tenantA2" ) + ->json_is( "/response/id" => $tenantA_id ) + ->json_is( "/response/active" => $true ) + ->json_is( "/response/parentId" => $root_tenant_id ) + ->json_is( "/alerts/0/level" => "success" ) + , 'Does the tenantA2 details return?'; + +#change "active" ok $t->put_ok('/api/1.2/tenants/' . $tenantA_id => {Accept => 'application/json'} => json => { - "name" => "tenantA2", "parentId" => $root_tenant_id + "name" => "tenantA2", "active" => $false, "parentId" => $root_tenant_id }) ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) ->json_is( "/response/name" => "tenantA2" ) ->json_is( "/response/id" => $tenantA_id ) + ->json_is( "/response/active" => $false ) ->json_is( "/response/parentId" => $root_tenant_id ) ->json_is( "/alerts/0/level" => "success" ) - , 'Does the tenant2 details return?'; + , 'Did we moved to non active?'; + +#change "active" back +ok $t->put_ok('/api/1.2/tenants/' . $tenantA_id => {Accept => 'application/json'} => json => { + "name" => "tenantA2", "active" => $true, "parentId" => $root_tenant_id + }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "tenantA2" ) + ->json_is( "/response/id" => $tenantA_id ) + ->json_is( "/response/active" => $true ) + ->json_is( "/response/parentId" => $root_tenant_id ) + ->json_is( "/alerts/0/level" => "success" ) + , 'Did we moved back to active?'; #cannot change tenant parent to undef ok $t->put_ok('/api/1.2/tenants/' . $tenantA_id => {Accept => 'application/json'} => json => { "name" => "tenantC", })->status_is(400); +#cannot change root-tenant to inactive +ok $t->put_ok('/api/1.2/tenants/' . $root_tenant_id => {Accept => 'application/json'} => json => { + "name" => "root", "active" => $false, "parentId" => undef + })->status_is(400); + #adding a child tenant ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { - "name" => "tenantD", "parentId" => $tenantA_id })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + "name" => "tenantD", "active" => $true, "parentId" => $tenantA_id })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) ->json_is( "/response/name" => "tenantD" ) + ->json_is( "/response/active" => $true ) + ->json_is( "/response/parentId" => $tenantA_id) + , 'Does the tenant details return?'; + +#adding a child inactive tenant +ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { + "name" => "tenantE", "active" => $false, "parentId" => $tenantA_id })->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "tenantE" ) + ->json_is( "/response/active" => $false ) ->json_is( "/response/parentId" => $tenantA_id) , 'Does the tenant details return?'; @@ -89,7 +133,9 @@ ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => { ok $t->delete_ok('/api/1.2/tenants/' . $tenantA_id)->status_is(500); my $tenantD_id = &get_tenant_id('tenantD'); +my $tenantE_id = &get_tenant_id('tenantE'); +ok $t->delete_ok('/api/1.2/tenants/' . $tenantE_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); ok $t->delete_ok('/api/1.2/tenants/' . $tenantD_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); ok $t->delete_ok('/api/1.2/tenants/' . $tenantA_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); From 7b5a241762baa528eab2484b4e9adf674c31e71f Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 03:18:24 +0200 Subject: [PATCH 10/18] Removing tenant non standard APIs --- traffic_ops/app/lib/API/Tenant.pm | 24 ------------------------ traffic_ops/app/lib/TrafficOpsRoutes.pm | 1 - 2 files changed, 25 deletions(-) diff --git a/traffic_ops/app/lib/API/Tenant.pm b/traffic_ops/app/lib/API/Tenant.pm index d5d5ea234a..6b12d3b1b5 100644 --- a/traffic_ops/app/lib/API/Tenant.pm +++ b/traffic_ops/app/lib/API/Tenant.pm @@ -50,7 +50,6 @@ sub index { "name" => $row->name, "active" => $row->active, "parentId" => $row->parent_id, - #"parentName" => $self->getTenantName($row->parent_id) } ); } @@ -58,26 +57,6 @@ sub index { } -sub index_by_name { - my $self = shift; - my $name = $self->param('name'); - - my $rs_data = $self->db->resultset("Tenant")->search( { 'me.name' => $name }); - my @data = (); - while ( my $row = $rs_data->next ) { - push( - @data, { - "id" => $row->id, - "name" => $row->name, - "active" => $row->active, - "parentId" => $row->parent_id, - #"parentName" => $self->getTenantName($row->parent_id) - } - ); - } - $self->success( \@data ); -} - sub show { my $self = shift; my $id = $self->param('id'); @@ -91,7 +70,6 @@ sub show { "name" => $row->name, "active" => $row->active, "parentId" => $row->parent_id, - #"parentName" => $self->getTenantName($row->parent_id) } ); } @@ -156,7 +134,6 @@ sub update { $response->{name} = $rs->name; $response->{active} = $rs->active; $response->{parentId} = $rs->parent_id; - #$response->{parentName} = $self->getTenantName($rs->parent_id); $response->{lastUpdated} = $rs->last_updated; &log( $self, "Updated Tenant name '" . $rs->name . "' for id: " . $rs->id, "APICHANGE" ); return $self->success( $response, "Tenant update was successful." ); @@ -211,7 +188,6 @@ sub create { $response->{name} = $rs->name; $response->{active} = $rs->active; $response->{parentId} = $rs->parent_id; - #$response->{parentName} = $self->getTenantName($rs->parent_id); $response->{lastUpdated} = $rs->last_updated; &log( $self, "Created Tenant name '" . $rs->name . "' for id: " . $rs->id, "APICHANGE" ); diff --git a/traffic_ops/app/lib/TrafficOpsRoutes.pm b/traffic_ops/app/lib/TrafficOpsRoutes.pm index fc67bc9818..58d422696a 100644 --- a/traffic_ops/app/lib/TrafficOpsRoutes.pm +++ b/traffic_ops/app/lib/TrafficOpsRoutes.pm @@ -554,7 +554,6 @@ sub api_routes { # -- TENANTS $r->get("/api/$version/tenants")->over( authenticated => 1 )->to( 'Tenant#index', namespace => $namespace ); $r->get( "/api/$version/tenants/:id" => [ id => qr/\d+/ ] )->over( authenticated => 1 )->to( 'Tenant#show', namespace => $namespace ); - $r->get( "/api/$version/tenants/name/:name")->over( authenticated => 1 )->to( 'Tenant#index_by_name', namespace => $namespace ); $r->put("/api/$version/tenants/:id")->over( authenticated => 1 )->to( 'Tenant#update', namespace => $namespace ); $r->post("/api/$version/tenants")->over( authenticated => 1 )->to( 'Tenant#create', namespace => $namespace ); $r->delete("/api/$version/tenants/:id")->over( authenticated => 1 )->to( 'Tenant#delete', namespace => $namespace ); From a7511aab0119eca4407920850fc2b28203057830 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 10:00:56 +0200 Subject: [PATCH 11/18] User create API --- traffic_ops/app/lib/API/User.pm | 112 ++++++++++++++++++++++++ traffic_ops/app/lib/TrafficOpsRoutes.pm | 2 + traffic_ops/app/lib/UI/Utils.pm | 7 +- 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/traffic_ops/app/lib/API/User.pm b/traffic_ops/app/lib/API/User.pm index 690b176de9..58eed7cd54 100644 --- a/traffic_ops/app/lib/API/User.pm +++ b/traffic_ops/app/lib/API/User.pm @@ -218,6 +218,118 @@ sub update { } +# Create +sub create { + my $self = shift; + my $params = $self->req->json; + + if ( !&is_oper($self) ) { + return $self->forbidden(); + } + + my $name = $params->{username}; + if ( !defined($name) ) { + return $self->alert("Username is required."); + } + + my $existing = $self->db->resultset('TmUser')->search( { username => $name } )->single(); + if ($existing) { + return $self->alert("A user with username \"$name\" already exists."); + } + + + if ( !defined($params->{fullName}) ) { + return $self->alert("full-name is required."); + } + + if ( !defined($params->{email}) ) { + return $self->alert("email is required."); + } + + if ( !defined($params->{localPassword}) ) { + return $self->alert("local-password is required."); + } + + if ( !defined($params->{confirmLocalPassword}) ) { + return $self->alert("confirm-local-password is required."); + } + + if ($params->{localPassword} ne $params->{confirmLocalPassword}){ + return $self->alert("local-password and confirmed-local-password mismatch."); + } + + if ( !defined($params->{role}) ) { + return $self->alert("role is required."); + } + + + + my $values = { + address_line1 => defined_or_default($params->{addressLine1}, ""), + address_line2 => defined_or_default($params->{addressLine2}, ""), + city => defined_or_default($params->{city}, ""), + company => defined_or_default($params->{company}, ""), + country => defined_or_default($params->{country}, ""), + email => $params->{email}, + full_name => $params->{fullName}, + new_user => ( $params->{newUser} ) ? 1 : 0, + phone_number => defined_or_default($params->{phoneNumber}, ""), + postal_code => defined_or_default($params->{postalCode}, ""), + public_ssh_key => defined_or_default($params->{publicSshKey}, ""), + registration_sent => defined_or_default( $params->{registrationSent}, undef), + role => $params->{role}, + state_or_province => defined_or_default($params->{stateOrProvince}, ""), + username => $params->{username}, + new_user => defined_or_default($params->{newUser}, 0), + uid => defined_or_default($params->{uid}, 0), + gid => defined_or_default($params->{gid}, 0), + local_passwd => sha1_hex($params->{localPassword} ), + confirm_local_passwd => sha1_hex($params->{confirmLocalPassword} ), + + }; + + my ( $is_valid, $result ) = $self->is_valid($values); + + if ( !$is_valid ) { + return $self->alert($result); + } + + my $insert = $self->db->resultset('TmUser')->create($values); + my $rs = $insert->insert(); + + if ($rs) { + my $response; + $response->{addressLine1} = $rs->address_line1; + $response->{addressLine2} = $rs->address_line2; + $response->{city} = $rs->city; + $response->{company} = $rs->company; + $response->{country} = $rs->country; + $response->{email} = $rs->email; + $response->{fullName} = $rs->full_name; + $response->{gid} = $rs->gid; + $response->{id} = $rs->id; + $response->{lastUpdated} = $rs->last_updated; + $response->{newUser} = \$rs->new_user; + $response->{phoneNumber} = $rs->phone_number; + $response->{postalCode} = $rs->postal_code; + $response->{publicSshKey} = $rs->public_ssh_key; + $response->{registrationSent} = \$rs->registration_sent; + $response->{role} = $rs->role->id; + $response->{roleName} = $rs->role->name; + $response->{stateOrProvince} = $rs->state_or_province; + $response->{uid} = $rs->uid; + $response->{username} = $rs->username; + + &log( $self, "Adding User with username '" . $rs->username . "' for id: " . $rs->id, "APICHANGE" ); + + return $self->success( $response, "User creation was successful." ); + } + else { + return $self->alert("User creation failed."); + } +} + + # Reset the User Profile password sub reset_password { my $self = shift; diff --git a/traffic_ops/app/lib/TrafficOpsRoutes.pm b/traffic_ops/app/lib/TrafficOpsRoutes.pm index 58d422696a..7fee0f2f38 100644 --- a/traffic_ops/app/lib/TrafficOpsRoutes.pm +++ b/traffic_ops/app/lib/TrafficOpsRoutes.pm @@ -712,6 +712,8 @@ sub api_routes { $r->get("/api/$version/users")->over( authenticated => 1 )->to( 'User#index', namespace => $namespace ); $r->get( "/api/$version/users/:id" => [ id => qr/\d+/ ] )->over( authenticated => 1 )->to( 'User#show', namespace => $namespace ); $r->put("/api/$version/users/:id")->over( authenticated => 1 )->to( 'User#update', namespace => $namespace ); + $r->post("/api/$version/users")->over( authenticated => 1 )->to( 'User#create', namespace => $namespace ); + # get all deliveryservices assigned to a user (from deliveryservice_tmuser table) $r->get( "/api/$version/users/:id/deliveryservices")->over( authenticated => 1 )->to( 'Deliveryservice#get_deliveryservices_by_userId', namespace => $namespace ); diff --git a/traffic_ops/app/lib/UI/Utils.pm b/traffic_ops/app/lib/UI/Utils.pm index ddeb2d6dd9..e6795bab04 100644 --- a/traffic_ops/app/lib/UI/Utils.pm +++ b/traffic_ops/app/lib/UI/Utils.pm @@ -42,7 +42,7 @@ use constant ADMIN => 30; our %EXPORT_TAGS = ( 'all' => [ qw(trim_whitespace is_admin is_oper is_ldap is_privileged log is_ipaddress is_ip6address is_netmask in_same_net is_hostname admin_status_id type_id type_ids - profile_id profile_ids tm_version tm_url name_version_string is_regexp stash_role navbarpage rascal_hosts_by_cdn is_steering) + profile_id profile_ids tm_version tm_url name_version_string is_regexp stash_role navbarpage rascal_hosts_by_cdn is_steering defined_or_default) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -401,4 +401,9 @@ sub exec_command { return $result; } +sub defined_or_default { + my $val = shift; + my $default = shift; + return defined($val) ? $val : $default; +} 1; From d5bdfbd9b6223be77830777c5b7cbfd696c70403 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 10:00:56 +0200 Subject: [PATCH 12/18] User create API - forgoten UT --- traffic_ops/app/t/api/1.2/user_admin.t | 86 ++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 traffic_ops/app/t/api/1.2/user_admin.t diff --git a/traffic_ops/app/t/api/1.2/user_admin.t b/traffic_ops/app/t/api/1.2/user_admin.t new file mode 100644 index 0000000000..aa345bddd3 --- /dev/null +++ b/traffic_ops/app/t/api/1.2/user_admin.t @@ -0,0 +1,86 @@ +package main; +# +# +# 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. +# +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use DBI; +use Data::Dumper; +use strict; +use warnings; +use Schema; +use Test::TestHelper; +use Fixtures::TmUser; +use Fixtures::Deliveryservice; +use Digest::SHA1 qw(sha1_hex); + +#no_transactions=>1 ==> keep fixtures after every execution, beware of duplicate data! +#no_transactions=>0 ==> delete fixtures after every execution + +BEGIN { $ENV{MOJO_MODE} = "test" } + +my $dbh = Schema->database_handle; +my $schema = Schema->connect_to_database; +my $t = Test::Mojo->new('TrafficOps'); + +Test::TestHelper->unload_core_data($schema); +Test::TestHelper->teardown( $schema, 'Log' ); +Test::TestHelper->teardown( $schema, 'Role' ); +Test::TestHelper->teardown( $schema, 'TmUser' ); + +Test::TestHelper->load_core_data($schema); + +ok my $portal_user = $schema->resultset('TmUser')->find( { username => Test::TestHelper::PORTAL_USER } ), 'Does the portal user exist?'; + +# Verify the Portal user +$t->post_ok( '/api/1.2/user/login', json => { u => Test::TestHelper::ADMIN_USER, p => Test::TestHelper::ADMIN_USER_PASSWORD } )->status_is(200); +$t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username", Test::TestHelper::ADMIN_USER ); + + +#adding a user +my $addedUserName = "user1"; +my $addedUserEmail = "abc\@z.com"; + +ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => $addedUserName, "fullName"=>"full name", "email" => $addedUserEmail, "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username" => $addedUserName ) + ->json_is( "/response/email" => $addedUserEmail) + , 'Failed adding user?'; + +#same name again - fail +ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => $addedUserName, "fullName"=>"full name", "email" => "xy\@z.com", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + , 'Success same user...'; + +#bad email - fail +ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => "user2", "fullName"=>"full name", "email" => "xy", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + , 'Success bad email...'; + +#adding same email again - fail +ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => "new-user", "fullName"=>"full name", "email" => $addedUserEmail, "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role`" => 4 }) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + , 'Success same email...'; + + +ok $t->post_ok('/api/1.2/user/logout')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); +$dbh->disconnect(); + +done_testing(); From ea0e54219523a8ad0d6315059c1c7f1aac17771e Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 13:53:57 +0200 Subject: [PATCH 13/18] Tenancy testing infra --- traffic_ops/app/lib/Fixtures/Tenant.pm | 2 +- traffic_ops/app/lib/Fixtures/TmUser.pm | 53 ++++++++++++++++++++++++++ traffic_ops/app/lib/Test/TestHelper.pm | 3 ++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/traffic_ops/app/lib/Fixtures/Tenant.pm b/traffic_ops/app/lib/Fixtures/Tenant.pm index e0c871a654..168e482764 100644 --- a/traffic_ops/app/lib/Fixtures/Tenant.pm +++ b/traffic_ops/app/lib/Fixtures/Tenant.pm @@ -23,7 +23,7 @@ my %definition_for = ( root_tenant_name => { new => 'Tenant', using => { - #id => 10**9, #a large number not to confuse the id sequence + id => 10**9, #a large number not to confuse the id sequence name => 'root', active => 1, parent_id => undef, diff --git a/traffic_ops/app/lib/Fixtures/TmUser.pm b/traffic_ops/app/lib/Fixtures/TmUser.pm index 2ed78babcd..cbb546b419 100644 --- a/traffic_ops/app/lib/Fixtures/TmUser.pm +++ b/traffic_ops/app/lib/Fixtures/TmUser.pm @@ -197,6 +197,59 @@ my %definition_for = ( registration_sent => '1999-01-01 00:00:00', }, }, + + admin_root => { + new => 'TmUser', + using => { + id => 800, + username => 'admin-root', + tenant_id => 10**9, + role => 4, + uid => '1', + gid => '1', + local_passwd => $local_passwd, + confirm_local_passwd => $local_passwd, + full_name => 'The Admin User for the "root" tenant', + email => 'admin-root@kabletown.com', + new_user => '1', + address_line1 => 'address_line1', + address_line2 => 'address_line2', + city => 'city', + state_or_province => 'state_or_province', + phone_number => '111-111-1111', + postal_code => '80122', + country => 'United States', + token => '', + registration_sent => '1999-01-01 00:00:00', + }, + }, + + portal_root => { + new => 'TmUser', + using => { + id => 900, + username => 'portal-root', + tenant_id => 10**9, + role => 6, + uid => '1', + gid => '1', + local_passwd => $local_passwd, + confirm_local_passwd => $local_passwd, + full_name => 'The Portal User for the "root" tenant', + email => 'portal-root@kabletown.com', + new_user => '1', + address_line1 => 'address_line3', + address_line2 => 'address_line4', + city => 'city', + state_or_province => 'state_or_province', + phone_number => '222-222-2222', + postal_code => '80122', + country => 'United States', + token => '', + registration_sent => '1999-01-01 00:00:00', + }, + }, + ); sub get_definition { diff --git a/traffic_ops/app/lib/Test/TestHelper.pm b/traffic_ops/app/lib/Test/TestHelper.pm index 2e85c7c5b4..e4395ed66d 100644 --- a/traffic_ops/app/lib/Test/TestHelper.pm +++ b/traffic_ops/app/lib/Test/TestHelper.pm @@ -65,6 +65,9 @@ use constant STEERING_PASSWORD_1 => 'password'; use constant STEERING_USER_2 => 'steering2'; use constant STEERING_PASSWORD_2 => 'password'; +use constant ADMIN_ROOT_USER => 'admin-root'; +use constant ADMIN_ROOT_USER_PASSWORD => 'password'; + sub load_all_fixtures { my $self = shift; my $fixture = shift; From 89def1a77ebe977676931f1a6f30d50a25686a7e Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 13:56:02 +0200 Subject: [PATCH 14/18] Tenancy code tools And first usage: CDN API support of tenancy: optional on create (use current user tenancy) --- traffic_ops/app/lib/API/Cdn.pm | 14 +++ traffic_ops/app/lib/UI/Utils.pm | 43 ++++++++- traffic_ops/app/t/api/1.2/cdn.t | 163 ++++++++++++++++++++------------ 3 files changed, 158 insertions(+), 62 deletions(-) diff --git a/traffic_ops/app/lib/API/Cdn.pm b/traffic_ops/app/lib/API/Cdn.pm index 875cd0f2e2..a89d368e5d 100644 --- a/traffic_ops/app/lib/API/Cdn.pm +++ b/traffic_ops/app/lib/API/Cdn.pm @@ -38,6 +38,7 @@ sub index { @data, { "id" => $row->id, "dnssecEnabled" => \$row->dnssec_enabled, + "tenantId" => $row->tenant_id, "lastUpdated" => $row->last_updated, "name" => $row->name } @@ -57,6 +58,7 @@ sub show { @data, { "id" => $row->id, "dnssecEnabled" => \$row->dnssec_enabled, + "tenantId" => $row->tenant_id, "lastUpdated" => $row->last_updated, "name" => $row->name } @@ -76,6 +78,7 @@ sub name { @data, { "id" => $row->id, "dnssecEnabled" => \$row->dnssec_enabled, + "tenantId" => $row->tenant_id, "lastUpdated" => $row->last_updated, "name" => $row->name } @@ -110,9 +113,13 @@ sub create { return $self->alert( "a cdn with name " . $params->{name} . " already exists." ); } + #setting tenant_id to the user's tenant if tenant is not set. TODO(nirs): remove when tenancy is no longer optional in the API + my $tenant_id = exists($params->{tenantId}) ? $params->{tenantId} : $self->current_user_tenant(); + my $values = { name => $params->{name}, dnssec_enabled => $params->{dnssecEnabled}, + tenant_id => $tenant_id, }; my $insert = $self->db->resultset('Cdn')->create($values); @@ -123,6 +130,7 @@ sub create { my $response; $response->{id} = $rs->id; $response->{name} = $rs->name; + $response->{tenantId} = $rs->tenant_id; $response->{dnssecEnabled} = \$rs->dnssec_enabled; &log( $self, "Created CDN with id: " . $rs->id . " and name: " . $rs->name, "APICHANGE" ); return $self->success( $response, "cdn was created." ); @@ -162,17 +170,23 @@ sub update { return $self->alert( "a cdn with name " . $params->{name} . " already exists." ); } + #setting tenant_id to undef if tenant is not set. TODO(nirs): remove when tenancy is no longer optional in the API + my $tenant_id = exists($params->{tenantId}) ? $params->{tenantId} : undef; + my $values = { name => $params->{name}, dnssec_enabled => $params->{dnssecEnabled}, + tenant_id => $tenant_id, }; + my $rs = $cdn->update($values); if ( $rs ) { my $response; $response->{id} = $rs->id; $response->{name} = $rs->name; $response->{dnssecEnabled} = \$rs->dnssec_enabled; + $response->{tenantId} = $rs->tenant_id; &log( $self, "Updated CDN name '" . $rs->name . "' for id: " . $rs->id, "APICHANGE" ); return $self->success( $response, "CDN update was successful." ); } diff --git a/traffic_ops/app/lib/UI/Utils.pm b/traffic_ops/app/lib/UI/Utils.pm index e6795bab04..615b0b19b3 100644 --- a/traffic_ops/app/lib/UI/Utils.pm +++ b/traffic_ops/app/lib/UI/Utils.pm @@ -42,7 +42,7 @@ use constant ADMIN => 30; our %EXPORT_TAGS = ( 'all' => [ qw(trim_whitespace is_admin is_oper is_ldap is_privileged log is_ipaddress is_ip6address is_netmask in_same_net is_hostname admin_status_id type_id type_ids - profile_id profile_ids tm_version tm_url name_version_string is_regexp stash_role navbarpage rascal_hosts_by_cdn is_steering defined_or_default) + profile_id profile_ids tm_version tm_url name_version_string is_regexp stash_role navbarpage rascal_hosts_by_cdn is_steering defined_or_default verify_tenancy current_user_tenant) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -401,9 +401,50 @@ sub exec_command { return $result; } + sub defined_or_default { my $val = shift; my $default = shift; return defined($val) ? $val : $default; } + +sub get_parent_tenant { + my $self = shift; + my $tenant_id = shift; + return defined($tenant_id) ? $self->db->resultset('Tenant')->search( { id => $tenant_id } )->get_column('parent_id')->single() : undef; +} + +sub verify_tenancy { + my $self = shift; + my $obj_tenancy = shift; + if (!defined($obj_tenancy)) { + #the object has no tenancy - opened for all + return 1; + } + my $user_tenant = current_user_tenant($self); + if (!defined($user_tenant)) { + #the user has no tenancy - cannot see items with tenancy + return 0; + } + + if ($user_tenant eq $obj_tenancy) { + #same tenancy + return 1; + } + while ( my $user_tenant = $self->get_parent_tenant($user_tenant )) { + if ($user_tenant eq $obj_tenancy) { + #parent tenancy + return 1; + } + }; + return 0; +} + +sub current_user_tenant { + my $self = shift; + my $username = shift; + return $self->db->resultset('TmUser')->search( { username => $self->current_user()->{username} } )->get_column('tenant_id')->single(); +} + + 1; diff --git a/traffic_ops/app/t/api/1.2/cdn.t b/traffic_ops/app/t/api/1.2/cdn.t index f9627190a2..32d80b9fa2 100644 --- a/traffic_ops/app/t/api/1.2/cdn.t +++ b/traffic_ops/app/t/api/1.2/cdn.t @@ -28,72 +28,113 @@ use Test::TestHelper; BEGIN { $ENV{MOJO_MODE} = "test" } +sub run_ut { + my $t = shift; + my $schema = shift; + my $login_user = shift; + my $login_password = shift; + + Test::TestHelper->unload_core_data($schema); + Test::TestHelper->load_core_data($schema); + + my $tenant_id = $schema->resultset('TmUser')->find( { username => $login_user } )->get_column('tenant_id'); + my $tenant_name = defined ($tenant_id) ? $schema->resultset('Tenant')->find( { id => $tenant_id } )->get_column('name') : "null"; + + + ok $t->post_ok( '/login', => form => { u => $login_user, p => $login_password} )->status_is(302) + ->or( sub { diag $t->tx->res->content->asset->{content}; } ), 'Tenant $tenant_name: Should login?'; + + $t->get_ok("/api/1.2/cdns")->status_is(200)->json_is( "/response/0/id", 100 ) + ->json_is( "/response/0/name", "cdn1" )->or( sub { diag $t->tx->res->content->asset->{content}; } ); + + $t->get_ok("/api/1.2/cdns/100")->status_is(200)->json_is( "/response/0/id", 100 ) + ->json_is( "/response/0/name", "cdn1" ) + ->or( sub { diag $t->tx->res->content->asset->{content}; } ); + + ok $t->post_ok('/api/1.2/cdns/100/queue_update' => {Accept => 'application/json'} => json => { + "action" => "queue" }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/cdnId" => 100 ) + ->json_is( "/response/action" => "queue" ) + , 'Tenant $tenant_name: Does the cdn details return?'; + + $t->get_ok("/api/1.2/servers?cdnId=100")->status_is(200) + ->json_is( "/response/0/updPending", 1 ) + ->json_is( "/response/1/updPending", 1 ) + ->or( sub { diag $t->tx->res->content->asset->{content}; } ); + + ok $t->post_ok('/api/1.2/cdns/100/queue_update' => {Accept => 'application/json'} => json => { + "action" => "dequeue" }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/cdnId" => 100 ) + ->json_is( "/response/action" => "dequeue" ) + , 'Tenant $tenant_name: Does the cdn details return?'; + + $t->get_ok("/api/1.2/servers?cdnId=100")->status_is(200) + ->json_is( "/response/0/updPending", 0 ) + ->json_is( "/response/1/updPending", 0 ) + ->or( sub { diag $t->tx->res->content->asset->{content}; } ); + + ok $t->post_ok('/api/1.2/cdns' => {Accept => 'application/json'} => json => { + "name" => "cdn_test", "dnssecEnabled" => "true", "tenantId" => $tenant_id }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "cdn_test" ) + ->json_is( "/response/tenantId", $tenant_id)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/level" => "success" ) + ->json_is( "/alerts/0/text" => "cdn was created." ) + , 'Tenant $tenant_name: Do the cdn queue update details return?'; + + my $cdn_id = &get_cdn_id('cdn_test'); + + ok $t->put_ok('/api/1.2/cdns/' . $cdn_id => {Accept => 'application/json'} => json => { + "name" => "cdn_test2", "dnssecEnabled" => "true", "tenantId" => $tenant_id + }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "cdn_test2" ) + ->json_is( "/response/tenantId", $tenant_id)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/level" => "success" ) + , 'Tenant $tenant_name: Does the cdn details return?'; + + ok $t->delete_ok('/api/1.2/cdns/' . $cdn_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); + + ok $t->put_ok('/api/1.2/cdns/' . $cdn_id => {Accept => 'application/json'} => json => { + "name" => "cdn_test3" + }) + ->status_is(404)->or( sub { diag $t->tx->res->content->asset->{content}; } ); + + #behvior when no tenant id set + ok $t->post_ok('/api/1.2/cdns' => {Accept => 'application/json'} => json => { + "name" => "cdn_test", "dnssecEnabled" => "true"}) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "cdn_test" ) + ->json_is( "/response/tenantId", $tenant_id)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/level" => "success" ) + ->json_is( "/alerts/0/text" => "cdn was created." ) + , 'Tenant $tenant_name: Do the cdn queue update details return?'; + + $cdn_id = &get_cdn_id('cdn_test'); + + ok $t->put_ok('/api/1.2/cdns/' . $cdn_id => {Accept => 'application/json'} => json => { + "name" => "cdn_test2", "dnssecEnabled" => "true" + }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "cdn_test2" ) + ->json_is( "/response/tenantId", undef)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/level" => "success" ) + , 'Tenant $tenant_name: Does the cdn details return?'; + + ok $t->delete_ok('/api/1.2/cdns/' . $cdn_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); + + ok $t->get_ok('/logout')->status_is(302)->or( sub { diag $t->tx->res->content->asset->{content}; } ); +} + my $schema = Schema->connect_to_database; my $dbh = Schema->database_handle; my $t = Test::Mojo->new('TrafficOps'); -Test::TestHelper->unload_core_data($schema); -Test::TestHelper->load_core_data($schema); - -ok $t->post_ok( '/login', => form => { u => Test::TestHelper::ADMIN_USER, p => Test::TestHelper::ADMIN_USER_PASSWORD } )->status_is(302) - ->or( sub { diag $t->tx->res->content->asset->{content}; } ), 'Should login?'; - -$t->get_ok("/api/1.2/cdns")->status_is(200)->json_is( "/response/0/id", 100 ) - ->json_is( "/response/0/name", "cdn1" )->or( sub { diag $t->tx->res->content->asset->{content}; } ); +run_ut($t, $schema, Test::TestHelper::ADMIN_USER, Test::TestHelper::ADMIN_USER_PASSWORD); +run_ut($t, $schema, Test::TestHelper::ADMIN_ROOT_USER, Test::TestHelper::ADMIN_ROOT_USER_PASSWORD); -$t->get_ok("/api/1.2/cdns/100")->status_is(200)->json_is( "/response/0/id", 100 ) - ->json_is( "/response/0/name", "cdn1" )->or( sub { diag $t->tx->res->content->asset->{content}; } ); - -ok $t->post_ok('/api/1.2/cdns/100/queue_update' => {Accept => 'application/json'} => json => { - "action" => "queue" }) - ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/cdnId" => 100 ) - ->json_is( "/response/action" => "queue" ) - , 'Does the cdn details return?'; - -$t->get_ok("/api/1.2/servers?cdnId=100")->status_is(200) - ->json_is( "/response/0/updPending", 1 ) - ->json_is( "/response/1/updPending", 1 ) - ->or( sub { diag $t->tx->res->content->asset->{content}; } ); - -ok $t->post_ok('/api/1.2/cdns/100/queue_update' => {Accept => 'application/json'} => json => { - "action" => "dequeue" }) - ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/cdnId" => 100 ) - ->json_is( "/response/action" => "dequeue" ) - , 'Does the cdn details return?'; - -$t->get_ok("/api/1.2/servers?cdnId=100")->status_is(200) - ->json_is( "/response/0/updPending", 0 ) - ->json_is( "/response/1/updPending", 0 ) - ->or( sub { diag $t->tx->res->content->asset->{content}; } ); - -ok $t->post_ok('/api/1.2/cdns' => {Accept => 'application/json'} => json => { - "name" => "cdn_test", "dnssecEnabled" => "true" }) - ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/name" => "cdn_test" ) - ->json_is( "/alerts/0/level" => "success" ) - ->json_is( "/alerts/0/text" => "cdn was created." ) - , 'Do the cdn queue update details return?'; - -my $cdn_id = &get_cdn_id('cdn_test'); - -ok $t->put_ok('/api/1.2/cdns/' . $cdn_id => {Accept => 'application/json'} => json => { - "name" => "cdn_test2", "dnssecEnabled" => "true" - }) - ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/name" => "cdn_test2" ) - ->json_is( "/alerts/0/level" => "success" ) - , 'Does the cdn details return?'; - -ok $t->delete_ok('/api/1.2/cdns/' . $cdn_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); - -ok $t->put_ok('/api/1.2/cdns/' . $cdn_id => {Accept => 'application/json'} => json => { - "name" => "cdn_test3" - }) - ->status_is(404)->or( sub { diag $t->tx->res->content->asset->{content}; } ); - -ok $t->get_ok('/logout')->status_is(302)->or( sub { diag $t->tx->res->content->asset->{content}; } ); $dbh->disconnect(); done_testing(); From 797b06c723ff86cc983a3e2e296f41c9bd1089d9 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 14:26:22 +0200 Subject: [PATCH 15/18] CDN UI: viewable for all but edit only for proper tenants Additionaly, a created cdn is with the creator tenancy --- traffic_ops/app/lib/Fixtures/Cdn.pm | 9 +++++++++ traffic_ops/app/lib/UI/Cdn.pm | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/traffic_ops/app/lib/Fixtures/Cdn.pm b/traffic_ops/app/lib/Fixtures/Cdn.pm index 781a0f1e6c..0fb8c234dd 100644 --- a/traffic_ops/app/lib/Fixtures/Cdn.pm +++ b/traffic_ops/app/lib/Fixtures/Cdn.pm @@ -37,6 +37,15 @@ my %definition_for = ( tenant_id => undef, }, }, + ## id => 3 + cdn_root_cdn_name => { + new => 'Cdn', + using => { + id => 300, + name => 'cdn-root', + tenant_id => 10**9, + }, + }, ); sub get_definition { diff --git a/traffic_ops/app/lib/UI/Cdn.pm b/traffic_ops/app/lib/UI/Cdn.pm index d9f9d80090..89f159b018 100644 --- a/traffic_ops/app/lib/UI/Cdn.pm +++ b/traffic_ops/app/lib/UI/Cdn.pm @@ -59,7 +59,7 @@ sub view { &stash_role($self); $self->stash( fbox_layout => 1, cdn_data => $data ); - if ( $mode eq "edit" and $self->stash('priv_level') > 20 ) { + if ( $mode eq "edit" and $self->stash('priv_level') > 20 and verify_tenancy($self, $data->tenant_id)) { $self->render( template => 'cdn/edit' ); } else { @@ -136,7 +136,9 @@ sub create { return $self->redirect_to( '/cdn/edit/' . $new_id ); } else { - my $insert = $self->db->resultset('Cdn')->create( { name => $name } ); + my $insert = $self->db->resultset('Cdn')->create( { name => $name, + tenant_id => current_user_tenant($self), #Tenancy is not dealt by the UI for now. getting the tenancy from the user + } ); $insert->insert(); $new_id = $insert->id; } From d84d42073df5f0d320ae2fddfca4837fa7a2fdee Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 16:16:22 +0200 Subject: [PATCH 16/18] CDN API UT improvement --- traffic_ops/app/t/api/1.2/cdn.t | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/traffic_ops/app/t/api/1.2/cdn.t b/traffic_ops/app/t/api/1.2/cdn.t index 32d80b9fa2..05bb0cd542 100644 --- a/traffic_ops/app/t/api/1.2/cdn.t +++ b/traffic_ops/app/t/api/1.2/cdn.t @@ -78,7 +78,7 @@ sub run_ut { ok $t->post_ok('/api/1.2/cdns' => {Accept => 'application/json'} => json => { "name" => "cdn_test", "dnssecEnabled" => "true", "tenantId" => $tenant_id }) ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/name" => "cdn_test" ) + ->json_is( "/response/name" => "cdn_test" )->or( sub { diag $t->tx->res->content->asset->{content}; } ) ->json_is( "/response/tenantId", $tenant_id)->or( sub { diag $t->tx->res->content->asset->{content}; } ) ->json_is( "/alerts/0/level" => "success" ) ->json_is( "/alerts/0/text" => "cdn was created." ) @@ -122,6 +122,26 @@ sub run_ut { ->json_is( "/response/tenantId", undef)->or( sub { diag $t->tx->res->content->asset->{content}; } ) ->json_is( "/alerts/0/level" => "success" ) , 'Tenant $tenant_name: Does the cdn details return?'; + + #putting tenancy back + ok $t->put_ok('/api/1.2/cdns/' . $cdn_id => {Accept => 'application/json'} => json => { + "name" => "cdn_test2", "dnssecEnabled" => "true", "tenantId" => $tenant_id + }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "cdn_test2" ) + ->json_is( "/response/tenantId", $tenant_id)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/level" => "success" ) + , 'Tenant $tenant_name: Does the cdn details return?'; + + #removing tenancy explictly + ok $t->put_ok('/api/1.2/cdns/' . $cdn_id => {Accept => 'application/json'} => json => { + "name" => "cdn_test2", "dnssecEnabled" => "true", "tenantId" => undef + }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/name" => "cdn_test2" ) + ->json_is( "/response/tenantId", undef)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/level" => "success" ) + , 'Tenant $tenant_name: Does the cdn details return?'; ok $t->delete_ok('/api/1.2/cdns/' . $cdn_id)->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); From 7889fac46adb5c8d42979a00151b38dac284ef91 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Tue, 28 Feb 2017 17:25:35 +0200 Subject: [PATCH 17/18] User API support tenantId as optional variable --- traffic_ops/app/lib/API/User.pm | 35 +++++-- traffic_ops/app/lib/Test/TestHelper.pm | 3 + traffic_ops/app/t/api/1.2/user.t | 119 ++++++++++++++++------ traffic_ops/app/t/api/1.2/user_admin.t | 131 ++++++++++++++++--------- 4 files changed, 202 insertions(+), 86 deletions(-) diff --git a/traffic_ops/app/lib/API/User.pm b/traffic_ops/app/lib/API/User.pm index 58eed7cd54..4fd5f270bc 100644 --- a/traffic_ops/app/lib/API/User.pm +++ b/traffic_ops/app/lib/API/User.pm @@ -97,7 +97,8 @@ sub index { "rolename" => $row->role->name, "stateOrProvince" => $row->state_or_province, "uid" => $row->uid, - "username" => $row->username + "username" => $row->username, + "tenantId" => $row->tenant_id } ); } @@ -132,7 +133,8 @@ sub show { "rolename" => $row->role->name, "stateOrProvince" => $row->state_or_province, "uid" => $row->uid, - "username" => $row->username + "username" => $row->username, + "tenantId" => $row->tenant_id } ); } @@ -159,6 +161,9 @@ sub update { return $self->not_found(); } + #setting tenant_id to undef if tenant is not set. TODO(nirs): remove when tenancy is no longer optional in the API + my $tenant_id = exists($params->{tenantId}) ? $params->{tenantId} : undef; + my $values = { address_line1 => $params->{addressLine1}, address_line2 => $params->{addressLine2}, @@ -171,10 +176,12 @@ sub update { phone_number => $params->{phoneNumber}, postal_code => $params->{postalCode}, public_ssh_key => $params->{publicSshKey}, - registration_sent => ( $params->{registrationSent} ) ? 1 : 0, + registration_sent => ( $params->{registrationSent} ) ? $params->{registrationSent} : undef, role => $params->{role}, state_or_province => $params->{stateOrProvince}, - username => $params->{username} + username => $params->{username}, + tenant_id => $tenant_id + }; if ( defined($params->{localPasswd}) && $params->{localPasswd} ne '' ) { @@ -207,6 +214,7 @@ sub update { $response->{stateOrProvince} = $rs->state_or_province; $response->{uid} = $rs->uid; $response->{username} = $rs->username; + $response->{tenantId} = $rs->tenant_id; &log( $self, "Updated User with username '" . $rs->username . "' for id: " . $rs->id, "APICHANGE" ); @@ -262,7 +270,8 @@ sub create { return $self->alert("role is required."); } - + #setting tenant_id to the user's tenant if tenant is not set. TODO(nirs): remove when tenancy is no longer optional in the API + my $tenant_id = exists($params->{tenantId}) ? $params->{tenantId} : $self->current_user_tenant(); my $values = { address_line1 => defined_or_default($params->{addressLine1}, ""), @@ -284,7 +293,8 @@ sub create { uid => defined_or_default($params->{uid}, 0), gid => defined_or_default($params->{gid}, 0), local_passwd => sha1_hex($params->{localPassword} ), - confirm_local_passwd => sha1_hex($params->{confirmLocalPassword} ), + confirm_local_passwd => sha1_hex($params->{confirmLocalPassword} ), + tenant_id => $tenant_id, }; @@ -319,6 +329,7 @@ sub create { $response->{stateOrProvince} = $rs->state_or_province; $response->{uid} = $rs->uid; $response->{username} = $rs->username; + $response->{tenantId} = $rs->tenant_id; &log( $self, "Adding User with username '" . $rs->username . "' for id: " . $rs->id, "APICHANGE" ); @@ -389,7 +400,8 @@ sub current { @data, { "id" => "0", "username" => $current_username, - "publicSshKey" => "", + "tenantId" => $self->current_user_tenant(), + "publicSshKey" => "", "role" => $role, "uid" => "0", "gid" => "0", @@ -433,6 +445,7 @@ sub current { "phoneNumber" => $row->phone_number, "postalCode" => $row->postal_code, "country" => $row->country, + "tenantId" => $row->tenant_id, } ); } @@ -448,7 +461,7 @@ sub update_current { if ( &is_ldap($self) ) { return $self->alert("Profile cannot be updated because '" . $user->{username} ."' is logged in as LDAP."); } - + my $db_user; # Prevent these from getting updated @@ -487,6 +500,10 @@ sub update_current { if ( defined( $user->{"username"} ) ) { $db_user->{"username"} = $user->{"username"}; } + if ( exists( $user->{"tenantId"} ) ) { + #if value is not set, it will be kept as is. Keeping consistency. Using "exists" and not "defined" to allow data clearing + $db_user->{"tenant_id"} = $user->{"tenantId"}; + } if ( defined( $user->{"public_ssh_key"} ) ) { $db_user->{"public_ssh_key"} = $user->{"public_ssh_key"}; } @@ -587,6 +604,8 @@ sub is_valid { return $self->is_username_taken( $value, $params ); } }, + + #TODO(nirs) MAYBE when tenancy is not optional, add a tenant not null check ] }; diff --git a/traffic_ops/app/lib/Test/TestHelper.pm b/traffic_ops/app/lib/Test/TestHelper.pm index e4395ed66d..2b8a240339 100644 --- a/traffic_ops/app/lib/Test/TestHelper.pm +++ b/traffic_ops/app/lib/Test/TestHelper.pm @@ -68,6 +68,9 @@ use constant STEERING_PASSWORD_2 => 'password'; use constant ADMIN_ROOT_USER => 'admin-root'; use constant ADMIN_ROOT_USER_PASSWORD => 'password'; +use constant PORTAL_ROOT_USER => 'portal-root'; +use constant PORTAL_ROOT_USER_PASSWORD => 'password'; + sub load_all_fixtures { my $self = shift; my $fixture = shift; diff --git a/traffic_ops/app/t/api/1.2/user.t b/traffic_ops/app/t/api/1.2/user.t index 3501e8c390..bda22c1a50 100644 --- a/traffic_ops/app/t/api/1.2/user.t +++ b/traffic_ops/app/t/api/1.2/user.t @@ -31,49 +31,104 @@ use Digest::SHA1 qw(sha1_hex); BEGIN { $ENV{MOJO_MODE} = "test" } -my $dbh = Schema->database_handle; -my $schema = Schema->connect_to_database; -my $t = Test::Mojo->new('TrafficOps'); -Test::TestHelper->unload_core_data($schema); -Test::TestHelper->teardown( $schema, 'Log' ); -Test::TestHelper->teardown( $schema, 'Role' ); -Test::TestHelper->teardown( $schema, 'TmUser' ); +sub run_ut { + my $t = shift; + my $schema = shift; + my $login_user = shift; + my $login_password = shift; + + Test::TestHelper->unload_core_data($schema); + Test::TestHelper->teardown( $schema, 'Log' ); + Test::TestHelper->teardown( $schema, 'Role' ); + Test::TestHelper->teardown( $schema, 'TmUser' ); + + Test::TestHelper->load_core_data($schema); + + my $tenant_id = $schema->resultset('TmUser')->find( { username => $login_user } )->get_column('tenant_id'); + my $tenant_name = defined ($tenant_id) ? $schema->resultset('Tenant')->find( { id => $tenant_id } )->get_column('name') : "null"; + + ok my $portal_user = $schema->resultset('TmUser')->find( { username => $login_user } ), 'Tenant $tenant_name: Does the portal user exist?'; -Test::TestHelper->load_core_data($schema); + # Verify the Portal user + $t->post_ok( '/api/1.2/user/login', json => { u => $login_user, p => $login_password} )->status_is(200); + $t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username", $login_user ) + ->json_is( "/response/tenantId", $tenant_id); -ok my $portal_user = $schema->resultset('TmUser')->find( { username => Test::TestHelper::PORTAL_USER } ), 'Does the portal user exist?'; + # Test required fields + $t->post_ok( '/api/1.2/user/current/update', + json => { user => { username => $login_user, email => 'testportal1@kabletown.com', address_line1 => 'newaddress', tenantId => $tenant_id} } ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/text", "UserProfile was successfully updated." ); + + #verify tenancy + $t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username", $login_user ) + ->json_is( "/response/tenantId", $tenant_id); -# Verify the Portal user -$t->post_ok( '/api/1.2/user/login', json => { u => Test::TestHelper::PORTAL_USER, p => Test::TestHelper::PORTAL_USER_PASSWORD } )->status_is(200); -$t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/username", Test::TestHelper::PORTAL_USER ); + # Test required fields + if (defined($tenant_id)){ + #verify the update with no "tenant" do not removed the tenant + $t->post_ok( '/api/1.2/user/current/update', + json => { user => { username => $login_user, email => 'testportal1@kabletown.com', address_line1 => 'newaddress'} } ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/text", "UserProfile was successfully updated." ); + #verify tenancy + $t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username", $login_user ) + ->json_is( "/response/tenantId", $tenant_id); -# Test required fields -$t->post_ok( '/api/1.2/user/current/update', - json => { user => { username => Test::TestHelper::PORTAL_USER, email => 'testportal1@kabletown.com', address_line1 => 'newaddress' } } ) - ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/text", "UserProfile was successfully updated." ); + #removed the tenant + $t->post_ok( '/api/1.2/user/current/update', + json => { user => { username => $login_user, email => 'testportal1@kabletown.com', address_line1 => 'newaddress', tenantId => undef} } ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/text", "UserProfile was successfully updated." ); + #verify tenancy + $t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username", $login_user ) + ->json_is( "/response/tenantId", undef); + + #putting the tenant back the update with no "tenant" removed the tenant + $t->post_ok( '/api/1.2/user/current/update', + json => { user => { username => $login_user, email => 'testportal1@kabletown.com', address_line1 => 'newaddress', tenantId => $tenant_id} } ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/text", "UserProfile was successfully updated." ); + #verify tenancy + $t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username", $login_user ) + ->json_is( "/response/tenantId", $tenant_id); + } + + # Ensure unique emails + ok $t->post_ok( '/api/1.2/user/current/update', json => { user => { username => $login_user, email => 'testportal1@kabletown.com', tenantId => $tenant_id } } ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/level", "success" ), + "Tenant $tenant_name: Verify that the emails are unique"; -# Ensure unique emails -ok $t->post_ok( '/api/1.2/user/current/update', json => { user => { username => Test::TestHelper::PORTAL_USER, email => 'testportal1@kabletown.com' } } ) - ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/level", "success" ), - "Verify that the emails are unique"; + ok $t->post_ok( '/api/1.2/user/current/update', json => { user => { username => $login_user, email => '@kabletown.com', tenantId => $tenant_id } } ) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/level", "error" ), + "Tenant $tenant_name: Verify that the emails are properly formatted"; -ok $t->post_ok( '/api/1.2/user/current/update', json => { user => { username => Test::TestHelper::PORTAL_USER, email => '@kabletown.com' } } ) - ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/level", "error" ), - "Verify that the emails are properly formatted"; + ok $t->post_ok( '/api/1.2/user/current/update', json => { user => { username => $login_user, email => '@kabletown.com', tenantId => $tenant_id } } ) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/level", "error" ), + "Tenant $tenant_name: Verify that the usernames are unique"; -ok $t->post_ok( '/api/1.2/user/current/update', json => { user => { username => Test::TestHelper::PORTAL_USER, email => '@kabletown.com' } } ) - ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/level", "error" ), - "Verify that the usernames are unique"; + $t->post_ok( '/api/1.2/user/current/update', json => { user => { email => 'testportal1@kabletown.com', tenantId => $tenant_id } } )->status_is(400) + ->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/text", "username is required" ); -$t->post_ok( '/api/1.2/user/current/update', json => { user => { email => 'testportal1@kabletown.com' } } )->status_is(400) - ->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/text", "username is required" ); + $t->post_ok( '/api/1.2/user/current/update', json => { user => { username => $login_user, tenantId => $tenant_id } } )->status_is(400) + ->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/text", "email is required" ); + + ok $t->post_ok('/api/1.2/user/logout')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); +} + +my $dbh = Schema->database_handle; +my $schema = Schema->connect_to_database; +my $t = Test::Mojo->new('TrafficOps'); -$t->post_ok( '/api/1.2/user/current/update', json => { user => { username => Test::TestHelper::PORTAL_USER } } )->status_is(400) - ->or( sub { diag $t->tx->res->content->asset->{content}; } )->json_is( "/alerts/0/text", "email is required" ); +run_ut($t, $schema, Test::TestHelper::PORTAL_USER, Test::TestHelper::PORTAL_USER_PASSWORD); +run_ut($t, $schema, Test::TestHelper::PORTAL_ROOT_USER, Test::TestHelper::PORTAL_ROOT_USER_PASSWORD); -ok $t->post_ok('/api/1.2/user/logout')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); $dbh->disconnect(); done_testing(); diff --git a/traffic_ops/app/t/api/1.2/user_admin.t b/traffic_ops/app/t/api/1.2/user_admin.t index aa345bddd3..9a654b3444 100644 --- a/traffic_ops/app/t/api/1.2/user_admin.t +++ b/traffic_ops/app/t/api/1.2/user_admin.t @@ -25,62 +25,101 @@ use Test::TestHelper; use Fixtures::TmUser; use Fixtures::Deliveryservice; use Digest::SHA1 qw(sha1_hex); +use Data::Dumper; #no_transactions=>1 ==> keep fixtures after every execution, beware of duplicate data! #no_transactions=>0 ==> delete fixtures after every execution BEGIN { $ENV{MOJO_MODE} = "test" } -my $dbh = Schema->database_handle; -my $schema = Schema->connect_to_database; -my $t = Test::Mojo->new('TrafficOps'); - -Test::TestHelper->unload_core_data($schema); -Test::TestHelper->teardown( $schema, 'Log' ); -Test::TestHelper->teardown( $schema, 'Role' ); -Test::TestHelper->teardown( $schema, 'TmUser' ); - -Test::TestHelper->load_core_data($schema); - -ok my $portal_user = $schema->resultset('TmUser')->find( { username => Test::TestHelper::PORTAL_USER } ), 'Does the portal user exist?'; - -# Verify the Portal user -$t->post_ok( '/api/1.2/user/login', json => { u => Test::TestHelper::ADMIN_USER, p => Test::TestHelper::ADMIN_USER_PASSWORD } )->status_is(200); -$t->get_ok('/api/1.2/user/current.json')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/username", Test::TestHelper::ADMIN_USER ); - - -#adding a user -my $addedUserName = "user1"; -my $addedUserEmail = "abc\@z.com"; - -ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { - "username" => $addedUserName, "fullName"=>"full name", "email" => $addedUserEmail, "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) - ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - ->json_is( "/response/username" => $addedUserName ) - ->json_is( "/response/email" => $addedUserEmail) - , 'Failed adding user?'; - -#same name again - fail -ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { - "username" => $addedUserName, "fullName"=>"full name", "email" => "xy\@z.com", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) - ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) - , 'Success same user...'; - -#bad email - fail -ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { - "username" => "user2", "fullName"=>"full name", "email" => "xy", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) - ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) +sub run_ut { + my $t = shift; + my $schema = shift; + my $login_user = shift; + my $login_password = shift; + + Test::TestHelper->unload_core_data($schema); + Test::TestHelper->teardown( $schema, 'Log' ); + Test::TestHelper->teardown( $schema, 'Role' ); + Test::TestHelper->teardown( $schema, 'TmUser' ); + + Test::TestHelper->load_core_data($schema); + + my $tenant_id = $schema->resultset('TmUser')->find( { username => $login_user } )->get_column('tenant_id'); + my $tenant_name = defined ($tenant_id) ? $schema->resultset('Tenant')->find( { id => $tenant_id } )->get_column('name') : "null"; + + # Verify the user + ok my $user = $schema->resultset('TmUser')->find( { username => $login_user } ), 'Does the portal user exist?'; + + ok $t->post_ok( '/login', => form => { u => $login_user, p => $login_password} )->status_is(302); + + #adding a user + my $addedUserName = "user1"; + my $addedUserEmail = "abc\@z.com"; + + ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => $addedUserName, "fullName"=>"full name", "email" => $addedUserEmail, "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/username" => $addedUserName ) + ->json_is( "/response/email" => $addedUserEmail) + , 'Failed adding user?'; + + #same name again - fail + ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => $addedUserName, "fullName"=>"full name1", "email" => "xy\@z.com", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + , 'Success same user...'; + + #bad email - fail + ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => "user2", "fullName"=>"full name2", "email" => "xy", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4 }) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) , 'Success bad email...'; -#adding same email again - fail -ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { - "username" => "new-user", "fullName"=>"full name", "email" => $addedUserEmail, "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role`" => 4 }) - ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + #adding same email again - fail + ok $t->post_ok('/api/1.2/users' => {Accept => 'application/json'} => json => { + "username" => "new-user", "fullName"=>"full name3", "email" => $addedUserEmail, "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role`" => 4 }) + ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } ) , 'Success same email...'; + + my $userid = $schema->resultset('TmUser')->find( { username => $addedUserName } )->id, 'Does the portal user exist?'; + + if (defined($tenant_id)){ + #verify the update with no "tenant" removed the tenant + $t->put_ok( '/api/1.2/users/'.$userid, + json => { "username" => $addedUserName."1", "fullName"=>"full name", "email" => $addedUserEmail."1", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4} ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/text", "User update was successful." ) + ->json_is( "/response/tenantId", undef); + + #putting the tenant back the tenant + $t->put_ok( '/api/1.2/users/'.$userid, + json => { "username" => $addedUserName."2", "tenantId" => $tenant_id, "fullName"=>"full name", "email" => $addedUserEmail."2", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4} ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/response/tenantId", $tenant_id) + ->json_is( "/alerts/0/text", "User update was successful." ); + + + #removed the tenant explicitly + $t->put_ok( '/api/1.2/users/'.$userid, + json => { "username" => $addedUserName."3", "tenantId" => undef, "fullName"=>"full name", "email" => $addedUserEmail."3", "localPassword" => "pass", "confirmLocalPassword"=> "pass", "role" => 4} ) + ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ) + ->json_is( "/alerts/0/text", "User update was successful." ) + ->json_is( "/response/tenantId", undef); + + } + + ok $t->get_ok('/logout')->status_is(302)->or( sub { diag $t->tx->res->content->asset->{content}; } ); +} + +my $schema = Schema->connect_to_database; +my $dbh = Schema->database_handle; +my $t = Test::Mojo->new('TrafficOps'); +run_ut($t, $schema, Test::TestHelper::ADMIN_USER, Test::TestHelper::ADMIN_USER_PASSWORD); +run_ut($t, $schema, Test::TestHelper::ADMIN_ROOT_USER, Test::TestHelper::ADMIN_ROOT_USER_PASSWORD); -ok $t->post_ok('/api/1.2/user/logout')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } ); $dbh->disconnect(); - done_testing(); + + From aad07f58529820f5602e60fb1f7430709e6e4759 Mon Sep 17 00:00:00 2001 From: nir-sopher Date: Wed, 1 Mar 2017 03:23:41 +0200 Subject: [PATCH 18/18] Adding an experimental go based rest API for Traffic Ops --- traffic_ops/experimental/go-api/api.go | 112 ++++++++++ .../go-api/tenant/EndPointSeeder.go | 202 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100755 traffic_ops/experimental/go-api/api.go create mode 100755 traffic_ops/experimental/go-api/tenant/EndPointSeeder.go diff --git a/traffic_ops/experimental/go-api/api.go b/traffic_ops/experimental/go-api/api.go new file mode 100755 index 0000000000..5fa5085036 --- /dev/null +++ b/traffic_ops/experimental/go-api/api.go @@ -0,0 +1,112 @@ +package main + +/* + 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. +*/ + +/* + This module was created in order to POC the concept of API Gateway between the different modules of Traffic-Ops, + specifically Old perl Traffic-Ops, and new (go?) one. + It launches up an RESTful server and supplies API towards Traffic-Ops DB. + At first step, only "tenant" table is covered (CRUD). + Other tables can be added, by adding modules (see tenant foe example), and seeding their enpoints below. + To Run this module, call: go run api.go --server --db-config-file + e.g. go run api.go --server :8888 --db-config-file ../../app/conf/test/database.conf + Note that the below go modules are required, so you'll might need to "go get" them:) + "github.com/gorilla/mux" + "github.com/lib/pq" +*/ + + +import ( + "database/sql" + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "os" + + "github.com/gorilla/mux" + _ "github.com/lib/pq" + +//modules to seed endpoints + "./tenant" +) + +type DbConfig struct { + Hostname string `json:"hostname"` + Port string `json:"port"` //String for now, as the conf files are like this + User string `json:"user"` + Password string `json:"password"` + DbName string `json:"dbname"` +} + +func main() { + + server := flag.String("server", "", "IP:port to listen on") + dbConfigFileName := flag.String("db-config-file", "", "DB to connect to config file") + + flag.Parse() + + if *server == ""{ + fmt.Println("Missing server address") + os.Exit(1) + } + + if *dbConfigFileName == ""{ + fmt.Println("Missing DB config file") + os.Exit(1) + } + + dbConfigFD, err := os.Open(*dbConfigFileName) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + var dbConfig DbConfig + jsonParser := json.NewDecoder(dbConfigFD) + if err := jsonParser.Decode(&dbConfig); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + psqlInfo := fmt.Sprintf("host=%s port=%s user=%s "+"password=%s dbname=%s sslmode=disable",dbConfig.Hostname, dbConfig.Port, dbConfig.User, dbConfig.Password, dbConfig.DbName) + db, err := sql.Open("postgres", psqlInfo) + if err != nil { + panic(err) + } + defer db.Close() + + err= db.Ping() + if err != nil { + panic(err) + } + + fmt.Println("Successfully connected!") + + + apiPrefix := "/api/2.0" + router := mux.NewRouter() + + + //list of elements to populat the api + tenantEndpoints := tenant.NewEndpointSeeder(db) + tenantEndpoints.Seed(router, apiPrefix) + + log.Fatal(http.ListenAndServe(*server, router)) +} + + + diff --git a/traffic_ops/experimental/go-api/tenant/EndPointSeeder.go b/traffic_ops/experimental/go-api/tenant/EndPointSeeder.go new file mode 100755 index 0000000000..ec3021e7a8 --- /dev/null +++ b/traffic_ops/experimental/go-api/tenant/EndPointSeeder.go @@ -0,0 +1,202 @@ +package tenant + +/* + 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. +*/ + + +import ( + "database/sql" + "encoding/json" + "net/http" + "fmt" + "time" + + "github.com/gorilla/mux" + _ "github.com/lib/pq" +) + + +//TODO(nirs) polymorphisem + +/////BASE - COMMON TO ALL SEEDERS (needs to move to another package) +/// Defintions +type EndpointSeeder struct{ + myDb *sql.DB +} + +/// Interface +func NewEndpointSeeder(aDb *sql.DB) *EndpointSeeder { + endPointSeeder := new(EndpointSeeder) + endPointSeeder.myDb = aDb + return endPointSeeder +} + +func (aEndpointSeeder *EndpointSeeder) Seed (aRouter *mux.Router, aApiPrefix string) { + + aRouter.HandleFunc(aApiPrefix+"/tenants", aEndpointSeeder.getListEndpoint).Methods("GET") + aRouter.HandleFunc(aApiPrefix+"/tenants/{id}", aEndpointSeeder.getEndpoint).Methods("GET") + aRouter.HandleFunc(aApiPrefix+"/tenants", aEndpointSeeder.createEndpoint).Methods("POST") + aRouter.HandleFunc(aApiPrefix+"/tenants/{id}", aEndpointSeeder.updateEndpoint).Methods("PUT") + aRouter.HandleFunc(aApiPrefix+"/tenants/{id}", aEndpointSeeder.deleteEndpoint).Methods("DELETE") +} + +///Common utilities +func (aEndpointSeeder *EndpointSeeder) checkErr(aError error) { + //TODO log + if aError != nil { + panic(aError) + } +} + + +/////Class specific + +type Tenant struct { + Id int `json:"id"` + Name string `json:"name"` + Active bool `json:"active"` + ParentId int `json:"parentId"` +} + +func (aEndpointSeeder *EndpointSeeder) getEndpoint(aResponseWriter http.ResponseWriter, aRequest *http.Request) { + params := mux.Vars(aRequest) + + var id int + var name string + var active bool + var parent_id sql.NullInt64 + var updated time.Time; + + err := aEndpointSeeder.myDb.QueryRow("SELECT * FROM tenant WHERE id="+params["id"]).Scan(&id, &name, &active, &parent_id, &updated) + if err == sql.ErrNoRows{//TODO make it work + aResponseWriter.WriteHeader(http.StatusNotFound) + fmt.Fprint(aResponseWriter, params["id"]+" not found") + return + } + aEndpointSeeder.checkErr(err) + + var parent_id1 int + if parent_id.Valid { + parent_id1 = int(parent_id.Int64) + } else { + parent_id1 = 0 + } + + json.NewEncoder(aResponseWriter).Encode(Tenant{Id: id, Name: name, Active: active, ParentId: parent_id1}) + return +} + + + +func (aEndpointSeeder *EndpointSeeder) getListEndpoint(aResponseWriter http.ResponseWriter, aRequest *http.Request) { + var tenants []Tenant + rows, err := aEndpointSeeder.myDb.Query("SELECT * FROM tenant") + aEndpointSeeder.checkErr(err) + + for rows.Next() { + var id int + var name string + var active bool + var parent_id sql.NullInt64 + var updated time.Time; + + err = rows.Scan(&id, &name, &active, &parent_id, &updated) + aEndpointSeeder.checkErr(err) + + var parent_id1 int + if parent_id.Valid { + parent_id1 = int(parent_id.Int64) + } else { + parent_id1 = 0 + } + + tenants = append(tenants, Tenant{Id: id, Name: name, Active: active, ParentId: parent_id1}) + } + + json.NewEncoder(aResponseWriter).Encode(tenants) + +} + +func (aEndpointSeeder *EndpointSeeder) createEndpoint(aResponseWriter http.ResponseWriter, aRequest *http.Request) { + var tenant Tenant + _ = json.NewDecoder(aRequest.Body).Decode(&tenant) + + var tenantParent sql.NullInt64 + if tenant.ParentId==0{ + tenantParent.Valid = false + }else{ + tenantParent.Int64 = int64(tenant.ParentId) + tenantParent.Valid = true + } + + + var lastInsertId int + err := aEndpointSeeder.myDb.QueryRow("INSERT INTO tenant(name, active, parent_id, last_updated) VALUES($1,$2,$3,$4) returning id;", tenant.Name, tenant.Active, tenantParent, "2012-12-09").Scan(&lastInsertId) + aEndpointSeeder.checkErr(err) + aEndpointSeeder.getListEndpoint(aResponseWriter, aRequest) +} + + + +func (aEndpointSeeder *EndpointSeeder) updateEndpoint(aResponseWriter http.ResponseWriter, aRequest *http.Request) { + params := mux.Vars(aRequest) + + var tenant Tenant + _ = json.NewDecoder(aRequest.Body).Decode(&tenant) + + var tenantParent sql.NullInt64 + if tenant.ParentId==0{ + tenantParent.Valid = false + }else{ + tenantParent.Int64 = int64(tenant.ParentId) + tenantParent.Valid = true + } + + stmt, err := aEndpointSeeder.myDb.Prepare("update tenant set name=$1, active=$2, parent_id=$3, last_updated=$4 where id="+params["id"]) + if err == sql.ErrNoRows{//TODO make it work + aResponseWriter.WriteHeader(http.StatusNotFound) + return + } + aEndpointSeeder.checkErr(err) + + res, err := stmt.Exec(tenant.Name, tenant.Active, tenantParent, "2012-12-09") + aEndpointSeeder.checkErr(err) + + _, err = res.RowsAffected() + aEndpointSeeder.checkErr(err) + + aEndpointSeeder.getEndpoint(aResponseWriter, aRequest) +} + +func (aEndpointSeeder *EndpointSeeder) deleteEndpoint(aResponseWriter http.ResponseWriter, aRequest *http.Request) { + params := mux.Vars(aRequest) + + stmt, err := aEndpointSeeder.myDb.Prepare("delete from tenant where id="+params["id"]) + if err == sql.ErrNoRows{//TODO make it work + aResponseWriter.WriteHeader(http.StatusNotFound) + return + } + aEndpointSeeder.checkErr(err) + + res, err := stmt.Exec() + aEndpointSeeder.checkErr(err) + + _, err = res.RowsAffected() + aEndpointSeeder.checkErr(err) + + aEndpointSeeder.getListEndpoint(aResponseWriter, aRequest) +} + + +