You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by mi...@apache.org on 2017/03/20 01:09:18 UTC

[4/8] incubator-trafficcontrol git commit: Tenant API

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 <ni...@gmail.com>
Authored: Wed Mar 15 15:50:58 2017 +0200
Committer: Jeremy Mitchell <mi...@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;
+}
+