Skip to content

Commit

Permalink
feat(provider/oracle): create/delete LoadBalancer
Browse files Browse the repository at this point in the history
  • Loading branch information
desagar authored and shihchang committed Oct 23, 2018
1 parent 4652ab9 commit aa36f49
Show file tree
Hide file tree
Showing 20 changed files with 1,579 additions and 3 deletions.
99 changes: 99 additions & 0 deletions app/scripts/modules/oracle/src/domain/IOracleLoadBalancer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ILoadBalancer, ILoadBalancerDeleteCommand, ILoadBalancerUpsertCommand } from '@spinnaker/core';
import { ISubnet } from '../../../core/src/domain';

export type ListenerProtocol = 'HTTP' | 'HTTPS' | 'TCP' | 'SSL';
export enum LoadBalancingPolicy {
ROUND_ROBIN = 'ROUND_ROBIN',
IP_HASH = 'IP_HASH',
LEAST_CONNECTIONS = 'LEAST_CONNECTIONS',
}

export interface IOracleSubnet extends ISubnet {
id: string;
name: string;
availabilityDomain: string;
securityListIds: string[];
vcnId: string;
}

export interface IOracleLoadBalancer extends ILoadBalancer {
shape: string; // required
isPrivate: boolean; // required
subnets: IOracleSubnet[]; // required 1 for private LB, 2 for public LB
certificates: IOracleListenerCertificate[];
listeners?: { [name: string]: IOracleListener }; // not required to create LB, but useless without it
hostnames?: IOracleHostname[];
backendSets?: { [name: string]: IOracleBackEndSet }; // not required to create LB, but useless without it
freeformTags?: { [tagName: string]: string };
id?: string; // not required to create LB
// TODO support path route sets
}

export interface IOracleListener {
name: string;
protocol: ListenerProtocol;
port: number;
defaultBackendSetName: string;
isSsl: boolean;
sslConfiguration?: IOracleListenerSSLConfiguration;
hostnames?: IOracleHostname[];
// TODO support pathRouteSets
}

export interface IOracleListenerSSLConfiguration {
certificateName: string;
verifyDepth: number;
verifyPeerCertificates: boolean;
}

export interface IOracleHostname {
name: string;
hostname: string;
}

export interface IOracleBackEndSet {
name: string;
policy: LoadBalancingPolicy;
healthChecker: IOracleBackendSetHealthCheck;
// TODO desagar sessionPersistenceConfiguration?: IOracleLoadBalancerSessionPersistenceConfiguration;
}

export interface IOracleListenerCertificate {
certificateName: string;
publicCertificate: string;
caCertificate: string;
privateKey: string;
passphrase: string;
}

export interface IOracleBackendSetHealthCheck {
urlPath: string; // required
protocol: 'HTTP' | 'TCP';
port: number;
intervalMillis?: number;
timeoutMillis?: number;
retries?: number;
returnCode?: number;
responseBodyRegex?: string;
}

/**
* IOracleLoadBalancerUpsertCommand is nearly identical to IOracleLoadBalancer - this object seems
* to be used for sending info to the back end from the UI.
*/
export interface IOracleLoadBalancerUpsertCommand extends ILoadBalancerUpsertCommand {
shape: string; // required
isPrivate: boolean; // required
subnetIds: string[]; // required 1 for private LB, 2 for public LB
listeners?: { [name: string]: IOracleListener }; // not required to create LB, but useless without it
hostnames?: IOracleHostname[];
backendSets?: { [name: string]: IOracleBackEndSet }; // not required to create LB, but useless without it
freeformTags?: { [tagName: string]: string };
loadBalancerType?: string; // is this needed because it is there in ILoadBalancer but not ILoadBalancerUpsertCommand??
securityGroups: string[]; // is this needed because it is there in ILoadBalancer but not ILoadBalancerUpsertCommand??
vpcId: string;
}

export interface IOracleLoadBalancerDeleteCommand extends ILoadBalancerDeleteCommand {
loadBalancerId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<div class="container-fluid form-horizontal">
<div class="form-group">
<div class="col-md-12">
<table class="table table-condensed packed">
<thead>
<tr>
<th>Name</th>
<th>Policy</th>
<th>Health Check Protocol</th>
<th>Health Check Port</th>
<th>Health Check Path</th>
<!-- TODO other nonrequired health check fields - do we need another popup for health check? -->
</tr>
</thead>
<tbody>
<tr ng-repeat="backendSet in ctrl.backendSets">
<td>
<input class="form-control input-sm" ng-model="backendSet.name" required
ng-focus="prevBackendSetNames[$index] = backendSet.name"
ng-blur="ctrl.backendSetNameChanged($index)"/>
</td>
<td>
<select class="form-control input-sm" ng-model="backendSet.policy" ng-options="policy for policy in ctrl.loadBalancingPolicies"></select>
</td>
<td>
<input class="form-control input-sm" ng-model="backendSet.healthChecker.protocol" required/>
</td>
<td>
<input class="form-control input-sm" ng-model="backendSet.healthChecker.port" required/>
</td>
<td>
<input class="form-control input-sm" ng-model="backendSet.healthChecker.urlPath" required/>
</td>
<td><a href class="sm-label" ng-click="ctrl.removeBackendSet($index)"><span
class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<button class="add-new col-md-12" ng-click="ctrl.addBackendSet()"><span class="glyphicon glyphicon-plus-sign"></span>
Add Backend Set
</button>
</td>
</tr>
</tfoot>
</table>

</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<div class="container-fluid form-horizontal">
<div class="form-group">
<div class="col-md-12">
<table class="table table-condensed packed">
<thead>
<tr>
<th>Name</th>
<th>Certificate</th>
<th>CA Cert</th>
<th>Private Key</th>
<th>Passphrase</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="cert in ctrl.certificates">
<td>
<input class="form-control input-sm" ng-model="cert.certificateName" required
ng-focus="prevCertNames[$index] = cert.name"
ng-blur="ctrl.certNameChanged($index)"/>
</td>
<td>
<textarea ng-model="cert.publicCertificate" required
class="form-control input-sm"></textarea>
</td>
<td>
<textarea ng-model="cert.caCertificate"
class="form-control input-sm"></textarea>
</td>
<td>
<textarea ng-model="cert.privateKey" required
class="form-control input-sm"></textarea>
</td>
<td>
<textarea ng-model="cert.passphrase"
class="form-control input-sm"></textarea>
</td>
<td><a href class="sm-label" ng-click="ctrl.removeCert($index)"><span
class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<button class="add-new col-md-12" ng-click="ctrl.addCert()"><span class="glyphicon glyphicon-plus-sign"></span>
Add Certificate
</button>
</td>
</tr>
</tfoot>
</table>

</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
'use strict';

import { IControllerService, IRootScopeService, IScope, mock, noop } from 'angular';
import { StateService } from '@uirouter/core';

import { API, APPLICATION_MODEL_BUILDER } from '@spinnaker/core';
import { ORACLE_LOAD_BALANCER_CREATE_CONTROLLER, OracleLoadBalancerController } from './createLoadBalancer.controller';
import { ApplicationModelBuilder } from 'core';
import {
IOracleLoadBalancer,
IOracleLoadBalancerUpsertCommand,
LoadBalancingPolicy,
} from 'oracle/domain/IOracleLoadBalancer';
import { OracleProviderSettings } from 'oracle/oracle.settings';

describe('Controller: oracleCreateLoadBalancerCtrl', function() {
let $http: ng.IHttpBackendService;
let controller: OracleLoadBalancerController;
const loadBalancer: IOracleLoadBalancer = null;
let $scope: IScope;
let $state: StateService;

beforeEach(mock.module(APPLICATION_MODEL_BUILDER, ORACLE_LOAD_BALANCER_CREATE_CONTROLLER));

// Initialize the controller and a mock scope
beforeEach(
mock.inject(
(
$controller: IControllerService,
$rootScope: IRootScopeService,
_$state_: StateService,
applicationModelBuilder: ApplicationModelBuilder,
) => {
$scope = $rootScope.$new();
$state = _$state_;
const application = applicationModelBuilder.createApplicationForTests('app', {
key: 'loadBalancers',
lazy: true,
});

const isNew = true;
controller = $controller(OracleLoadBalancerController, {
$scope,
$uibModalInstance: { dismiss: noop, result: { then: noop } },
loadBalancer,
application,
$state,
isNew,
});
controller.addBackendSet();
controller.addListener();
controller.addCert();

controller.listeners[0].defaultBackendSetName = controller.backendSets[0].name;
},
),
);

beforeEach(
mock.inject(function($httpBackend: ng.IHttpBackendService) {
// Set up the mock http service responses
$http = $httpBackend;
}),
);

function initListenerSslConfig() {
controller.listeners[0].isSsl = true;
controller.listenerIsSslChanged(controller.listeners[0]);
}

it('should have an instantiated controller', function() {
expect(controller).toBeDefined();
});

it('correctly creates a default loadbalancer', function() {
const lb: IOracleLoadBalancerUpsertCommand = $scope.loadBalancerCmd;
expect(lb.cloudProvider).toEqual('oracle');
expect(lb.credentials).toEqual(OracleProviderSettings.defaults.account);
expect(lb.region).toEqual(OracleProviderSettings.defaults.region);
expect(lb.isPrivate).toEqual(false);
expect($scope.existingLoadBalancerNames).toEqual(undefined);
});

it('correctly creates default listener', function() {
expect(controller.listeners).toBeDefined();
expect(controller.listeners.length).toEqual(1);
expect(controller.listeners[0].name).toEqual('HTTP_80');
expect(controller.listeners[0].protocol).toEqual('HTTP');
expect(controller.listeners[0].port).toEqual(80);
});

it('correctly creates default subnet', function() {
expect(controller.backendSets).toBeDefined();
expect(controller.backendSets.length).toEqual(1);
expect(controller.backendSets[0].name).toEqual('backendSet1');
expect(controller.backendSets[0].policy).toEqual(LoadBalancingPolicy.ROUND_ROBIN);
expect(controller.backendSets[0].healthChecker.protocol).toEqual('HTTP');
expect(controller.backendSets[0].healthChecker.port).toEqual(80);
expect(controller.backendSets[0].healthChecker.urlPath).toEqual('/healthZ');
});

it('correctly creates default certificate', function() {
expect(controller.certificates).toBeDefined();
expect(controller.certificates.length).toEqual(1);
expect(controller.certificates[0].certificateName).toEqual('certificate1');
});

it('changed backend set name updates listener', function() {
expect(controller.listeners[0].defaultBackendSetName).toEqual('backendSet1');
controller.backendSets[0].name = 'UpdatedBackendSetName';
controller.backendSetNameChanged(0);
expect(controller.listeners[0].defaultBackendSetName).toEqual('UpdatedBackendSetName');
});

it('remove backend set updates listener', function() {
controller.removeBackendSet(0);
expect(controller.listeners[0].defaultBackendSetName).not.toBeDefined();
});

it('sslConfiguration created on listener', function() {
expect(controller.listeners[0].sslConfiguration).not.toBeDefined();
initListenerSslConfig();
expect(controller.listeners[0].sslConfiguration).toBeDefined();
});

it('sslConfiguration removed on listener when isSsl turned off', function() {
initListenerSslConfig();
expect(controller.listeners[0].sslConfiguration).toBeDefined();
controller.listeners[0].isSsl = false;
controller.listenerIsSslChanged(controller.listeners[0]);
expect(controller.listeners[0].sslConfiguration).not.toBeDefined();
});

it('changed certificate name updates listener', function() {
initListenerSslConfig();
controller.listeners[0].sslConfiguration.certificateName = controller.certificates[0].certificateName;
expect(controller.listeners[0].sslConfiguration.certificateName).toEqual('certificate1');
controller.certificates[0].certificateName = 'someOtherCertName';
controller.certNameChanged(0);
expect(controller.listeners[0].sslConfiguration.certificateName).toEqual('someOtherCertName');
});

it('remove certificate updates listener', function() {
initListenerSslConfig();
controller.listeners[0].sslConfiguration.certificateName = controller.certificates[0].certificateName;
controller.removeCert(0);
expect(controller.listeners[0].sslConfiguration.certificateName).not.toBeDefined();
});

it('makes the expected REST calls for data for a new loadbalancer', function() {
$http.expect('GET', API.baseUrl + '/networks/oracle').respond([]);
$http.expect('GET', API.baseUrl + '/subnets/oracle').respond([]);
$http.flush();
$http.verifyNoOutstandingExpectation();
$http.verifyNoOutstandingRequest();
});
});
Loading

0 comments on commit aa36f49

Please sign in to comment.