Tenant API

Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/da494df7
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/da494df7
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/da494df7

Branch: refs/heads/master
Commit: da494df7d1891d95703f55eb15272dbfa92bf260
Parents: d936a34
Author: nir-sopher <nirsop...@gmail.com>
Authored: Wed Mar 15 15:50:58 2017 +0200
Committer: Jeremy Mitchell <mitchell...@gmail.com>
Committed: Sun Mar 19 19:08:28 2017 -0600

----------------------------------------------------------------------
 traffic_ops/app/lib/API/Tenant.pm       | 247 +++++++++++++++++++++++++++
 traffic_ops/app/lib/TrafficOpsRoutes.pm |   7 +
 traffic_ops/app/t/api/1.2/tenant.t      | 155 +++++++++++++++++
 3 files changed, 409 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/da494df7/traffic_ops/app/lib/API/Tenant.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/app/lib/API/Tenant.pm 
b/traffic_ops/app/lib/API/Tenant.pm
new file mode 100644
index 0000000..309b99c
--- /dev/null
+++ b/traffic_ops/app/lib/API/Tenant.pm
@@ -0,0 +1,247 @@
+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 !defined($self->db->resultset('Tenant')->search( { id => 
$tenant_id } )->get_column('parent_id')->single());
+}
+
+sub index {
+       my $self        = shift;
+
+       my @data;
+       my $orderby = $self->param('orderby') || "name";
+       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,
+                       }
+               );
+       }
+       $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,
+                               "active"       => \$row->active,
+                               "parentId"     => $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 $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.");
+       }
+       
+       my $values = {
+               name      => $params->{name},
+               active    => $params->{active},
+               parent_id => $params->{parentId}
+       };
+
+       my $rs = $tenant->update($values);
+       if ($rs) {
+               my $response;
+               $response->{id}          = $rs->id;
+               $response->{name}        = $rs->name;
+               $response->{active}      = \$rs->active;
+               $response->{parentId}    = $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 $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}
+       };
+
+       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->{active}             = \$rs->active;
+               $response->{parentId}           = $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 $name = $self->db->resultset('Tenant')->search( { id => $id } 
)->get_column('name')->single();
+       
+       my $existing_child = $self->db->resultset('Tenant')->search( { 
parent_id => $id } )->get_column('name')->first();
+       if ($existing_child) {
+               return $self->alert("Tenant '$name' has children tenant(s): e.g 
'$existing_child'. Please update these tenants and retry.");
+       }
+
+       #The order of the below tests is intentional - allowing UT to cover all 
cases - TODO(nirs) remove this comment when a full "tenancy" UT is added, 
including permissions and such (no use in putting effort into it yet)
+       #TODO(nirs) - add back when making available: my $existing_ds = 
$self->db->resultset('Deliveryservice')->search( { tenant_id => $id 
})->get_column('xml_id')->first();
+       #TODO(nirs) - add back when making available: if ($existing_ds) {
+       #TODO(nirs) - add back when making available:   return 
$self->alert("Tenant '$name' is assign with delivery-services(s): e.g. 
'$existing_ds'. Please update/delete these delivery-services and retry.");
+       #TODO(nirs) - add back when making available: }
+
+       #TODO(nirs) - add back when making available: my $existing_cdn = 
$self->db->resultset('Cdn')->search( { tenant_id => $id 
})->get_column('name')->first();
+       #TODO(nirs) - add back when making available: if ($existing_cdn) {
+       #TODO(nirs) - add back when making available:   return 
$self->alert("Tenant '$name' is assign with CDNs(s): e.g. '$existing_cdn'. 
Please update/delete these CDNs and retry.");
+       #TODO(nirs) - add back when making available: }
+
+       #TODO(nirs) - add back when making available: my $existing_user = 
$self->db->resultset('TmUser')->search( { tenant_id => $id 
})->get_column('username')->first();
+       #TODO(nirs) - add back when making available: if ($existing_user) {
+       #TODO(nirs) - add back when making available:   return 
$self->alert("Tenant '$name' is assign with user(s): e.g. '$existing_user'. 
Please update these users and retry.");
+       #TODO(nirs) - add back when making available: }
+
+       my $rs = $tenant->delete();
+       if ($rs) {
+               return $self->success_message("Tenant deleted.");
+       } else {
+               return $self->alert( "Tenant delete failed." );
+       }
+}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/da494df7/traffic_ops/app/lib/TrafficOpsRoutes.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/app/lib/TrafficOpsRoutes.pm 
b/traffic_ops/app/lib/TrafficOpsRoutes.pm
index 2b960eb..083636a 100644
--- a/traffic_ops/app/lib/TrafficOpsRoutes.pm
+++ b/traffic_ops/app/lib/TrafficOpsRoutes.pm
@@ -557,6 +557,13 @@ 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->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 );

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/da494df7/traffic_ops/app/t/api/1.2/tenant.t
----------------------------------------------------------------------
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 0000000..21b78b4
--- /dev/null
+++ b/traffic_ops/app/t/api/1.2/tenant.t
@@ -0,0 +1,155 @@
+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?';
+
+#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}; } );;
+
+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" =>  0)
+       ->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", "active" => 1, "parentId" => $root_tenant_id 
})->status_is(400);
+
+#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);
+
+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" => 1, "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" => 1 )
+               ->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", "active" => 0, "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" => 0 )
+               ->json_is( "/response/parentId" => $root_tenant_id )
+               ->json_is( "/alerts/0/level" => "success" )
+       , '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" => 1, "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" => 1 )
+               ->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" => 0, "parentId" => undef  
+               })->status_is(400);
+
+#adding a child tenant
+ok $t->post_ok('/api/1.2/tenants' => {Accept => 'application/json'} => json => 
{
+        "name" => "tenantD", "active" => 1, "parentId" => $tenantA_id 
})->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )
+       ->json_is( "/response/name" => "tenantD" )
+       ->json_is( "/response/active" => 1 )
+       ->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" => 0, "parentId" => $tenantA_id 
})->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )
+       ->json_is( "/response/name" => "tenantE" )
+       ->json_is( "/response/active" => 0 )
+       ->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(400)
+       ->json_is( "/alerts/0/text" => "Tenant 'tenantA2' has children 
tenant(s): e.g 'tenantD'. Please update these tenants and retry." )
+       ->or( sub { diag $t->tx->res->content->asset->{content}; } );
+
+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}; } );
+
+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;
+}
+

Reply via email to