aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzhihui wu <wu.zhihui1@zte.com.cn>2018-03-01 02:30:57 +0000
committerGerrit Code Review <gerrit@opnfv.org>2018-03-01 02:30:57 +0000
commitce37a4fa011c253465f6485dbd05f2c2e99f5109 (patch)
treeb6bd8a1607632abbc8ea069378da2135e6ba6337
parentc29e36280713aa232a19e0d3612bae0948601837 (diff)
parent15ac21b3a3b72f8a1242e8a72cdec20ab4494186 (diff)
Merge "support rfc2544 test using spirent virtual test center"
-rw-r--r--contrib/nettest/Dockerfile47
-rw-r--r--contrib/nettest/README.md0
-rw-r--r--contrib/nettest/nettest/heat_2stcv.yaml170
-rw-r--r--contrib/nettest/nettest/nettest.py157
-rw-r--r--contrib/nettest/nettest/requirements.txt9
-rw-r--r--contrib/nettest/nettest/rest_server.py343
-rw-r--r--contrib/nettest/nettest/rfc2544test.py576
-rw-r--r--contrib/nettest/nettest/start.sh11
-rw-r--r--contrib/nettest/nettest/stcv_stack.py174
9 files changed, 1487 insertions, 0 deletions
diff --git a/contrib/nettest/Dockerfile b/contrib/nettest/Dockerfile
new file mode 100644
index 00000000..a0ecabf9
--- /dev/null
+++ b/contrib/nettest/Dockerfile
@@ -0,0 +1,47 @@
+##########################################################
+# Dockerfile to run a flask-based web application# Based on an ubuntu:16.04
+##########################################################
+
+# Set the base image to use to centos
+FROM ubuntu:16.04
+
+# Set the file maintainer
+MAINTAINER Qiang.Dai@spirent.com
+LABEL version="0.1" description="Spirent networking test Docker container"
+
+# Set env varibles used in this Dockerfile (add a unique prefix, such as DOCKYARD)
+# Local directory with project source
+ENV DOCKYARD_SRC=nettest \
+ DOCKYARD_SRCHOME=/opt \
+ DOCKYARD_SRCPROJ=/opt/nettest
+
+# Update the defualt application repository source list
+RUN apt-get update && apt-get install -y \
+ gcc \
+ python-dev \
+ python-pip \
+ python-setuptools \
+ --no-install-recommends \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy application source code to SRCDIR
+COPY $DOCKYARD_SRC $DOCKYARD_SRCPROJ
+
+# Create application subdirectories
+WORKDIR $DOCKYARD_SRCPROJ
+RUN mkdir -p log
+VOLUME ["$DOCKYARD_SRCPROJ/log/"]
+
+# Install Python dependencies
+RUN pip install -U pip \
+ && pip install -U setuptools \
+ && pip install -r $DOCKYARD_SRCPROJ/requirements.txt
+
+# Port to expose
+EXPOSE 5001
+
+# Copy entrypoint script into the image
+WORKDIR $DOCKYARD_SRCPROJ
+
+#CMD ["/bin/bash"]
+CMD ["/bin/bash", "start.sh"]
diff --git a/contrib/nettest/README.md b/contrib/nettest/README.md
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/contrib/nettest/README.md
diff --git a/contrib/nettest/nettest/heat_2stcv.yaml b/contrib/nettest/nettest/heat_2stcv.yaml
new file mode 100644
index 00000000..77c6e6e8
--- /dev/null
+++ b/contrib/nettest/nettest/heat_2stcv.yaml
@@ -0,0 +1,170 @@
+##############################################################################
+# Copyright (c) 2018 Spirent Communications and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+heat_template_version: 2016-10-14
+
+description: Template for deploying 2 STCv and 1 labserver
+
+parameters:
+ public_net_name: {default: external, description: Public network to allocate floating IPs to VMs', type: string}
+ #public_net_id: {description: public_network id for exernal connectivity,type: string}
+ mgmt_net_name: {default: admin, description: Name of STCv mgmt network to be created, type: string}
+ mgmt_net_cidr: {default: 10.10.10.0/24, description: STCv mgmt network CIDR,type: string}
+ mgmt_net_gw: {default: 10.10.10.1, description: STCv mgmt network gateway address, type: string}
+ mgmt_net_pool_start: {default: 10.10.10.10, description: Start of mgmt network IP address allocation pool, type: string}
+ mgmt_net_pool_end: {default: 10.10.10.20, description: End of mgmt network IP address allocation pool, type: string}
+ tst_net_name: {default: tst, description: Name of STCv private network to be created, type: string}
+ tst_net_cidr: {default: 192.168.1.0/24, description: STCv private network CIDR,type: string}
+ tst_net_gw: {default: 192.168.1.1, description: STCv private network gateway address, type: string}
+ tst_net_pool_start: {default: 192.168.1.10, description: Start of private network IP address allocation pool, type: string}
+ tst_net_pool_end: {default: 192.168.1.20, description: End of private network IP address allocation pool, type: string}
+ stcv_image: {default: "stcv-4.79", description: Image name to use for STCv, type: string}
+ stcv_flavor: {default: "m1.tiny", description: Flavor to use for STCv, type: string}
+ #stcv_user_data: {default: "", description: user data such as ntp server ip for stcv, type: string}
+ #stcv_config_file: {default: "stcv_config_file", description: user data such as ntp server ip for stcv, type: string}
+ ntp_server_ip: {default: "", description: user data such as ntp server ip for stcv, type: string}
+ stcv_sg_name: {default: stcv_sg, description: server group name, type: string}
+ stcv_sg_affinity: {default: affinity, description: server group affinity for stcv, type: string}
+
+resources:
+ stcv_server_group:
+ type: OS::Nova::ServerGroup
+ properties:
+ name: {get_param: stcv_sg_name}
+ policies: [{get_param: stcv_sg_affinity}]
+ mgmt_net:
+ type: OS::Neutron::Net
+ properties:
+ name: {get_param: mgmt_net_name}
+ mgmt_net_subnet:
+ type: OS::Neutron::Subnet
+ properties:
+ allocation_pools:
+ - end: {get_param: mgmt_net_pool_end}
+ start: {get_param: mgmt_net_pool_start}
+ cidr: {get_param: mgmt_net_cidr}
+ gateway_ip: {get_param: mgmt_net_gw}
+ network: {get_resource: mgmt_net}
+ public_router:
+ type: OS::Neutron::Router
+ properties:
+ external_gateway_info:
+ network: {get_param: public_net_name}
+ router_interface:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router: {get_resource: public_router}
+ subnet: {get_resource: mgmt_net_subnet}
+ tst_net:
+ type: OS::Neutron::Net
+ properties:
+ name: {get_param: tst_net_name}
+ tst_subnet:
+ type: OS::Neutron::Subnet
+ properties:
+ allocation_pools:
+ - end: {get_param: tst_net_pool_end}
+ start: {get_param: tst_net_pool_start}
+ cidr: {get_param: tst_net_cidr}
+ gateway_ip: {get_param: tst_net_gw}
+ network: {get_resource: tst_net}
+ stcv_1_port_1:
+ type: OS::Neutron::Port
+ properties:
+ network: {get_resource: mgmt_net}
+ fixed_ips:
+ - subnet: {get_resource: mgmt_net_subnet}
+ floating_ip1:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network: {get_param: public_net_name}
+ port_id: {get_resource: stcv_1_port_1}
+ stcv_1_port_2:
+ type: OS::Neutron::Port
+ properties:
+ network: {get_resource: tst_net}
+ port_security_enabled: False
+ fixed_ips:
+ - subnet: {get_resource: tst_subnet}
+ STCv_1:
+ type: OS::Nova::Server
+ properties:
+ #availability_zone : {get_param: availability_zone_name}
+ flavor: {get_param: stcv_flavor}
+ image: {get_param: stcv_image}
+ name: STCv_1
+ user_data:
+ str_replace:
+ template: |
+ #cloud-config
+ spirent:
+ ntp: $ntp_server_ip
+ params:
+ $ntp_server_ip: {get_param: ntp_server_ip}
+ user_data_format: RAW
+ config_drive: True
+ scheduler_hints:
+ group: {get_resource: stcv_server_group}
+ networks:
+ - port: {get_resource: stcv_1_port_1}
+ - port: {get_resource: stcv_1_port_2}
+ stcv_2_port_1:
+ type: OS::Neutron::Port
+ properties:
+ network: {get_resource: mgmt_net}
+ fixed_ips:
+ - subnet: {get_resource: mgmt_net_subnet}
+ floating_ip2:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network: {get_param: public_net_name}
+ port_id: {get_resource: stcv_2_port_1}
+ stcv_2_port_2:
+ type: OS::Neutron::Port
+ properties:
+ network: {get_resource: tst_net}
+ port_security_enabled: False
+ fixed_ips:
+ - subnet: {get_resource: tst_subnet}
+ STCv_2:
+ type: OS::Nova::Server
+ properties:
+ #availability_zone : {get_param: availability_zone_name}
+ flavor: {get_param: stcv_flavor}
+ image: {get_param: stcv_image}
+ name: STCv_2
+ user_data:
+ str_replace:
+ template: |
+ #cloud-config
+ spirent:
+ ntp: $ntp_server_ip
+ params:
+ $ntp_server_ip: {get_param: ntp_server_ip}
+ user_data_format: RAW
+ config_drive: True
+ scheduler_hints:
+ group: {get_resource: stcv_server_group}
+ networks:
+ - port: {get_resource: stcv_2_port_1}
+ - port: {get_resource: stcv_2_port_2}
+outputs:
+ STCv_1_Mgmt_Ip:
+ value: {get_attr: [floating_ip1, floating_ip_address]}
+ description: STCv_1 Mgmt IP
+ STCv_2_Mgmt_Ip:
+ value: {get_attr: [floating_ip2, floating_ip_address]}
+ description: STCv_2 Mgmt IP
+ STCv_1_Tst_Ip:
+ value: {get_attr: [stcv_1_port_2, fixed_ips]}
+ description: STCv_1 Tst IP
+ STCv_2_Tst_Ip:
+ value: {get_attr: [stcv_2_port_2, fixed_ips]}
+ description: STCv_2 Tst IP
+
diff --git a/contrib/nettest/nettest/nettest.py b/contrib/nettest/nettest/nettest.py
new file mode 100644
index 00000000..c5a203e0
--- /dev/null
+++ b/contrib/nettest/nettest/nettest.py
@@ -0,0 +1,157 @@
+##############################################################################
+# Copyright (c) 2018 Spirent Communications and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import logging
+from time import sleep
+
+from rfc2544test import StcRfc2544Test
+from stcv_stack import StcvStack
+
+
+class NetTestMaster(object):
+
+ def __init__(self):
+ self.logger = logging.getLogger(__name__)
+
+ self.stacks = []
+ self.testcases = []
+
+ self.stack_created = False
+ self.status_reason = ''
+
+ def get_stack_by_id(self, id):
+ for stack in self.stacks:
+ if id == stack.stack_id:
+ return stack
+ return None
+
+ def get_stack_by_name(self, name):
+ for stack in self.stacks:
+ if name == stack.name:
+ return stack
+ return None
+
+ def create_stack(self, name, stack_type, pub_net_name, **kwargs):
+ if stack_type != 'stcv':
+ raise Exception('only support stcv stack type currently')
+
+ try:
+ stack = StcvStack(name=name,
+ pub_net_name=pub_net_name,
+ ntp_server_ip=kwargs.get('license_server_ip'),
+ lab_server_ip=kwargs.get('lab_server_ip'),
+ stcv_image=kwargs.get('stcv_image'),
+ stcv_flavor=kwargs.get('stcv_flavor'),
+ stcv_affinity=kwargs.get('stcv_affinity'))
+ stack.create_stack()
+ self.stacks.append(stack)
+
+ except Exception as err:
+ self.logger.error('create stack fail. err = %s', str(err))
+ raise err
+
+ return stack
+
+ def delete_stack(self, stack_id):
+ stack = self.get_stack_by_id(stack_id)
+ if stack is None:
+ raise Exception('stack does not exist, stack_id = %s', stack_id)
+
+ self.stacks.remove(stack)
+ stack.delete_stack()
+
+ def get_tc_result(self, tc_id):
+ tc = self.get_tc_by_id(tc_id)
+ return tc.get_result()
+
+ def get_tc_status(self, tc_id):
+ tc = self.get_tc_by_id(tc_id)
+ return tc.get_status()
+
+ def execute_testcase(self, name, category, stack_id, **kwargs):
+ if category != 'rfc2544':
+ raise Exception("currently only support rfc2544 test")
+
+ stack = self.get_stack_by_id(stack_id)
+ if stack is None:
+ raise Exception("defined stack not exist, stack_id = %s", stack_id)
+
+ tc = StcRfc2544Test(name=name,
+ lab_server_ip=stack.lab_server_ip,
+ license_server_ip=stack.ntp_server_ip,
+ west_stcv_admin_ip=stack.get_west_stcv_ip(),
+ west_stcv_tst_ip=stack.get_west_stcv_tst_ip(),
+ east_stcv_admin_ip=stack.get_east_stcv_ip(),
+ east_stcv_tst_ip=stack.get_east_stcv_tst_ip(),
+ stack_id=stack_id,
+ **kwargs)
+ self.testcases.append(tc)
+ tc.execute()
+
+ return tc.tc_id
+
+ def get_tc_by_id(self, id):
+ for tc in self.testcases:
+ if id == tc.tc_id:
+ return tc
+ return None
+
+ def delete_testcase(self, tc_id):
+ tc = self.get_tc_by_id(tc_id)
+
+ if tc.status == 'finished':
+ tc.delete_result()
+
+ if tc.status == 'running':
+ tc.cancel_run()
+
+ self.testcases.remove(tc)
+
+
+if __name__ == "__main__":
+ try:
+ nettest = NetTestMaster()
+ stack_params = {
+ "stcv_affinity": True,
+ "stcv_image": "stcv-4.79",
+ "stcv_flavor": "m1.tiny",
+ "lab_server_ip": "192.168.37.122",
+ "license_server_ip": "192.168.37.251"
+ }
+
+ stack = nettest.create_stack(name='stack1',
+ stack_type='stcv',
+ pub_net_name='external',
+ **stack_params)
+ tc_params = {
+ 'metric': 'throughput',
+ 'framesizes': [64, 128]
+ }
+ tc = nettest.execute_testcase(name='tc1',
+ category='rfc2544',
+ stack_id=stack.stack_id,
+ **tc_params)
+
+ print "test case id is %s" % tc.id
+
+ status = tc.get_status()
+ while (status != tc.TC_STATUS_FINISHED):
+ if status == tc.TC_STATUS_ERROR:
+ print "tc exectue fail, reason %s" % tc.get_err_reason()
+ break
+ sleep(2)
+ if status == tc.TC_STATUS_FINISHED:
+ print tc.get_result()
+
+ nettest.delete_testcase(tc.id)
+
+ nettest.delete_stack(stack.stack_id)
+
+ except Exception as err:
+ print err
diff --git a/contrib/nettest/nettest/requirements.txt b/contrib/nettest/nettest/requirements.txt
new file mode 100644
index 00000000..3efb124b
--- /dev/null
+++ b/contrib/nettest/nettest/requirements.txt
@@ -0,0 +1,9 @@
+flask
+flask_cors
+flask_restful
+flask_restful_swagger
+#openstacksdk
+keystoneauth1
+python-heatclient
+stcrestclient
+
diff --git a/contrib/nettest/nettest/rest_server.py b/contrib/nettest/nettest/rest_server.py
new file mode 100644
index 00000000..ee13c91b
--- /dev/null
+++ b/contrib/nettest/nettest/rest_server.py
@@ -0,0 +1,343 @@
+##############################################################################
+# Copyright (c) 2018 Spirent Communications and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import logging
+
+from flask import Flask, abort, jsonify, request
+from flask_cors import CORS
+from flask_restful import Api, Resource, fields
+from flask_restful_swagger import swagger
+
+from nettest import NetTestMaster
+
+app = Flask(__name__)
+CORS(app)
+api = swagger.docs(Api(app), apiVersion="1.0")
+
+stcv_master = NetTestMaster()
+
+
+@swagger.model
+class StackRequestModel:
+ resource_fields = {
+ 'stack_name': fields.String,
+ 'stack_type': fields.String,
+ 'public_network': fields.String,
+ "stack_params": fields.Nested,
+ }
+
+
+@swagger.model
+class StackResponseModel:
+ resource_fields = {
+ 'stack_name': fields.String,
+ 'stack_created': fields.Boolean,
+ "stack_id": fields.String
+ }
+
+
+class Stack(Resource):
+ def __init__(self):
+ self.logger = logging.getLogger(__name__)
+
+ @swagger.operation(
+ notes='Fetch the stack configuration',
+ parameters=[
+ {
+ "name": "id",
+ "description": "The UUID of the stack in the format "
+ "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
+ "required": True,
+ "type": "string",
+ "allowMultiple": False,
+ "paramType": "query"
+ },
+ ],
+ type=StackResponseModel.__name__
+ )
+ def get(self):
+ stack_id = request.args.get('id')
+ stack = stcv_master.get_stack_by_id(stack_id)
+
+ if not stack:
+ abort(404)
+
+ return jsonify({
+ 'stack_name': stack.name,
+ 'stack_created': True,
+ "stack_id": stack_id})
+
+ @swagger.operation(
+ notes='''set the current agent configuration and create a stack in
+ the controller. Returns once the stack create is completed.''',
+ parameters=[
+ {
+ "name": "stack",
+ "description": '''Configuration to be set. All parameters are
+ necessory.
+ ''',
+ "required": True,
+ "type": "StackRequestModel",
+ "paramType": "body"
+ }
+ ],
+ type=StackResponseModel.__name__
+ )
+ def post(self):
+ if not request.json:
+ abort(400, "ERROR: No data specified")
+
+ self.logger.info(request.json)
+
+ try:
+ params = {
+ 'lab_server_ip': request.json['stack_params'].get('lab_server_ip'),
+ 'license_server_ip': request.json['stack_params'].get('license_server_ip'),
+ 'stcv_image': request.json['stack_params'].get('stcv_image'),
+ 'stcv_flavor': request.json['stack_params'].get('stcv_flavor'),
+ 'stcv_affinity': request.json['stack_params'].get('stcv_affinity')
+ }
+
+ stack = stcv_master.create_stack(name=request.json['stack_name'],
+ stack_type=request.json['stack_type'],
+ pub_net_name=request.json['public_network'],
+ **params)
+ if stack is None:
+ abort(400, "ERROR: create stack fail")
+
+ return jsonify({'stack_name': request.json['stack_name'],
+ 'stack_created': True,
+ 'stack_id': stack.stack_id})
+
+ except Exception as e:
+ abort(400, str(e))
+
+ @swagger.operation(
+ notes='delete deployed stack',
+ parameters=[
+ {
+ "name": "id",
+ "description": "The UUID of the stack in the format "
+ "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
+ "required": True,
+ "type": "string",
+ "allowMultiple": False,
+ "paramType": "query"
+ },
+ ],
+ responseMessages=[
+ {
+ "code": 200,
+ "message": "Stack ID found, response in JSON format"
+ },
+ {
+ "code": 404,
+ "message": "Stack ID not found"
+ }
+ ]
+ )
+ def delete(self):
+ try:
+ stack_id = request.args.get('id')
+ stcv_master.delete_stack(stack_id)
+ except Exception as e:
+ abort(400, str(e))
+
+
+@swagger.model
+class TestcaseRequestModel:
+ resource_fields = {
+ 'name': fields.String,
+ 'category': fields.String,
+ 'stack_id': fields.String,
+ 'params': fields.Nested
+ }
+
+
+@swagger.model
+class TestcaseResponseModel:
+ resource_fields = {
+ 'name': fields.String,
+ 'category': fields.String,
+ 'stack_id': fields.String,
+ 'tc_id': fields.String
+ }
+
+
+class TestCase(Resource):
+
+ """TestCase API"""
+
+ def __init__(self):
+ self.logger = logging.getLogger(__name__)
+
+ @swagger.operation(
+ notes='Fetch the metrics of the specified testcase',
+ parameters=[
+ {
+ "name": "id",
+ "description": "The UUID of the testcase in the format "
+ "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
+ "required": True,
+ "type": "string",
+ "allowMultiple": False,
+ "paramType": "query"
+ },
+ {
+ "name": "type",
+ "description": "The type of metrics to report. May be "
+ "metrics (default), metadata, or status",
+ "required": True,
+ "type": "string",
+ "allowMultiple": False,
+ "paramType": "query"
+ }
+ ],
+ responseMessages=[
+ {
+ "code": 200,
+ "message": "Workload ID found, response in JSON format"
+ },
+ {
+ "code": 404,
+ "message": "Workload ID not found"
+ }
+ ]
+ )
+ def get(self):
+ tc_id = request.args.get('id')
+ query_type = request.args.get('type')
+ ret = {}
+
+ try:
+ tc = stcv_master.get_tc_by_id(tc_id)
+ if query_type == "result":
+ ret = tc.get_result()
+
+ if query_type == "status":
+ status = tc.get_status()
+ ret['status'] = status
+ if 'error' == status:
+ reason = tc.get_err_reason()
+ ret['reason'] = reason
+
+ return jsonify(ret)
+
+ except Exception as err:
+ abort(400, str(err))
+
+ @swagger.operation(
+ parameters=[
+ {
+ "name": "body",
+ "description": """Start execution of a testcase with the
+parameters, only support rfc25cc test
+ """,
+ "required": True,
+ "type": "TestcaseRequestModel",
+ "paramType": "body"
+ }
+ ],
+ type=TestcaseResponseModel.__name__,
+ responseMessages=[
+ {
+ "code": 200,
+ "message": "TestCase submitted"
+ },
+ {
+ "code": 400,
+ "message": "Missing configuration data"
+ }
+ ]
+ )
+ def post(self):
+ if not request.json:
+ abort(400, "ERROR: Missing configuration data")
+
+ self.logger.info(request.json)
+
+ try:
+ name = request.json['name']
+ category = request.json['category']
+ stack_id = request.json['stack_id']
+ tc_id = stcv_master.execute_testcase(name=request.json['name'],
+ category=request.json['category'],
+ stack_id=request.json['stack_id'],
+ **request.json['params'])
+
+ return jsonify({'name': name,
+ 'category': category,
+ 'stack_id': stack_id,
+ 'tc_id': tc_id})
+
+ except Exception as e:
+ abort(400, str(e))
+
+ @swagger.operation(
+ notes='Cancels the currently running testcase or delete testcase result',
+ parameters=[
+ {
+ "name": "id",
+ "description": "The UUID of the testcase in the format "
+ "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
+ "required": True,
+ "type": "string",
+ "allowMultiple": False,
+ "paramType": "query"
+ },
+ ],
+ responseMessages=[
+ {
+ "code": 200,
+ "message": "Wordload ID found, response in JSON format"
+ },
+ ]
+ )
+ def delete(self):
+ try:
+ tc_id = request.args.get("id")
+ self.logger.info("receive delete testcase msg. tc_id = %s", tc_id)
+
+ stcv_master.delete_testcase(tc_id)
+
+ except Exception as e:
+ abort(400, str(e))
+
+
+api.add_resource(Stack, "/api/v1.0/stack")
+api.add_resource(TestCase, "/api/v1.0/testcase")
+
+'''
+@app.route("/")
+def hello_world():
+ return 'hello world'
+
+@app.route("/testcases")
+def get_testcases():
+ return []
+
+
+@app.route("/testcases/<int: tc_id>")
+def query_testcase(tc_id):
+ return []
+
+@app.route("/stctest/api/v1.0/testcase/<string: tc_name>", methods = ['GET'])
+def query_tc_result(tc_name):
+ return []
+
+@app.route("/stctest/api/v1.0/testcase", methods = ['POST'])
+def execut_testcase():
+ return []
+'''
+
+
+if __name__ == "__main__":
+ logger = logging.getLogger("nettest").setLevel(logging.DEBUG)
+
+ app.run(host="0.0.0.0", debug=True, threaded=True)
diff --git a/contrib/nettest/nettest/rfc2544test.py b/contrib/nettest/nettest/rfc2544test.py
new file mode 100644
index 00000000..688b4d12
--- /dev/null
+++ b/contrib/nettest/nettest/rfc2544test.py
@@ -0,0 +1,576 @@
+##############################################################################
+# Copyright (c) 2018 Spirent Communications and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import base64
+import copy
+import logging
+import os
+import shutil
+import threading
+from time import sleep
+import uuid
+
+import requests
+from stcrestclient import stchttp
+
+
+class Stcv2Net1Stack(object):
+ ADMIN_NETWORK_NAME = "admin"
+ ADMIN_SUBNET_ADDR = "50.50.50.0/24"
+ ADMIN_GW_IP = "50.50.50.1"
+ TST_NETWORK_NAME = "tst"
+ TST_SUBNET_ADDR = "192.168.0.0/24"
+ TST_GW_IP = "192.168.0.1"
+ ROUTER_NAME = "router"
+ WEST_STCV_NAME = "west_stcv"
+ EAST_STCV_NAME = "east_stcv"
+ AFFINITY_SG_NAME = "affinity"
+ STCV_USER_DATA = '''#cloud-config
+spirent:
+ ntp: '''
+
+ def __init__(self, name, conn, ext_network_name, params):
+ self.logger = logging.getLogger(__name__)
+
+ self.name = name
+ self.conn = conn
+ self.ext_network_name = ext_network_name
+ self.image_name = params['stcv_image']
+ self.flavor_name = params['stcv_flavor']
+ self.ntp_server_ip = params['license_server_ip']
+ self.affinity = params['stcv_affinity']
+
+ self.stack_id = str(uuid.uuid4())
+ self.admin_network = None
+ self.admin_subnet = None
+ self.tst_network = None
+ self.tst_subnet = None
+ self.ext_network = None
+ self.router = None
+ self.affinity_sg = None
+
+ self.west_stcv = None
+ self.west_stcv_ip = ''
+ self.east_stcv = None
+ self.east_stcv_ip = ''
+
+ def _deploy_test_network(self):
+
+ # create tst network and subnet
+ self.tst_network = self.conn.network.create_network(
+ name=self.TST_NETWORK_NAME)
+ self.tst_subnet = self.conn.network.create_subnet(
+ name=self.TST_NETWORK_NAME + '_subnet',
+ network_id=self.tst_network.id,
+ ip_version='4',
+ cidr=self.TST_SUBNET_ADDR,
+ gateway_ip=self.TST_GW_IP,
+ is_dhcp_enabled=True)
+
+ # create admin network and subnet
+ self.admin_network = self.conn.network.create_network(
+ name=self.ADMIN_NETWORK_NAME)
+ self.admin_subnet = self.conn.network.create_subnet(
+ name=self.ADMIN_NETWORK_NAME + '_subnet',
+ network_id=self.admin_network.id,
+ ip_version='4',
+ cidr=self.ADMIN_SUBNET_ADDR,
+ gateway_ip=self.ADMIN_GW_IP,
+ is_dhcp_enabled=True)
+
+ # create external gateway and connect admin subnet to router
+ self.ext_network = self.conn.network.find_network(self.ext_network_name)
+ self.router = self.conn.network.create_router(name=self.ROUTER_NAME,
+ external_gateway_info={"network_id": self.ext_network.id},
+ is_admin_state_up=True)
+ self.conn.network.add_interface_to_router(self.router, subnet_id=self.admin_subnet.id)
+
+ def _depoly_stcv(self, name, image_id, flavor_id, scheduler_hints, user_data):
+
+ stcv = self.conn.compute.create_server(
+ name=name, image_id=image_id, flavor_id=flavor_id,
+ networks=[{"uuid": self.admin_network.id}, {"uuid": self.tst_network.id}],
+ config_drive=True,
+ user_data=base64.encodestring(user_data)
+ )
+ stcv = self.conn.compute.wait_for_server(stcv)
+
+ stcv_fixed_ip = stcv.addresses[self.admin_network.name][0]['addr']
+ stcv_floating_ip = self.conn.network.create_ip(floating_network_id=self.ext_network.id)
+ self.conn.compute.add_floating_ip_to_server(server=stcv, address=stcv_floating_ip.floating_ip_address,
+ fixed_address=stcv_fixed_ip)
+
+ return {'stcv': stcv, 'fixed_ip': stcv_fixed_ip, 'floating_ip': stcv_floating_ip}
+
+ def create_stack(self):
+
+ image = self.conn.compute.find_image(self.image_name)
+ flavor = self.conn.compute.find_flavor(self.flavor_name)
+
+ if self.affinity:
+ self.affinity_sg = \
+ self.conn.compute.create_server_group(name=self.AFFINITY_SG_NAME,
+ policies=["affinity"])
+ else:
+ self.affinity_sg = \
+ self.conn.compute.create_server_group(name=self.AFFINITY_SG_NAME,
+ policies=["anti-affinity"])
+ self._deploy_test_network()
+
+ user_data = self.STCV_USER_DATA + self.ntp_server_ip
+
+ stcv = self._depoly_stcv(name=self.WEST_STCV_NAME,
+ image_id=image.id,
+ flavor_id=flavor.id,
+ scheduler_hints=self.affinity_sg,
+ user_data=user_data)
+ self.west_stcv = stcv['stcv']
+ self.west_stcv_ip = stcv['floating_ip']
+
+ stcv = self._depoly_stcv(name=self.EAST_STCV_NAME,
+ image_id=image.id,
+ flavor_id=flavor.id,
+ scheduler_hints=self.affinity_sg,
+ user_data=user_data)
+ self.east_stcv = stcv['stcv']
+ self.east_stcv_ip = stcv['floating_ip']
+
+ def delete_stack(self):
+
+ self.conn.compute.delete_server(self.west_stcv, ignore_missing=True)
+ self.conn.compute.delete_server(self.east_stcv, ignore_missing=True)
+
+ self.conn.compute.delete_server_group(server_group=self.affinity_sg,
+ ignore_missing=True)
+
+ # delete external gateway
+ self.conn.network.delete_router(self.router, ignore_missing=True)
+
+ # delete tst network
+ self.conn.network.delete_subnet(self.tst_subnet, ignore_missing=True)
+ self.conn.network.delete_network(self.tst_network, ignore_missing=True)
+
+ # delete admin network
+ self.conn.network.delete_subnet(self.admin_subnet, ignore_missing=True)
+ self.conn.network.delete_network(self.admin_network, ignore_missing=True)
+
+
+class StcSession:
+ """ wrapper class for stc session"""
+
+ def __init__(self, labserver_addr, user_name, session_name):
+ self.logger = logging.getLogger(__name__)
+
+ # create connection obj
+ self.stc = stchttp.StcHttp(labserver_addr)
+ self.user_name = user_name
+ self.session_name = session_name
+
+ # create session on labserver
+ self.session_id = self.stc.new_session(self.user_name, self.session_name)
+ self.stc.join_session(self.session_id)
+ return
+
+ def __del__(self):
+ # destroy resource on labserver
+ self.stc.end_session()
+
+ def clean_all_session(self):
+ session_urls = self.stc.session_urls()
+ for session in session_urls:
+ resp = requests.delete(session)
+ self.logger.info("delete session resp: %s", str(resp))
+ return
+
+
+class StcRfc2544Test:
+ """ RFC2544 test class"""
+
+ RESULT_PATH_PREFIX = './tc_results/rfc2544/'
+ TC_STATUS_INIT = 'init'
+ TC_STATUS_RUNNING = 'running'
+ TC_STATUS_FINISHED = 'finished'
+ TC_STATUS_ERROR = 'error'
+
+ default_additional_params = {
+ "AcceptableFrameLoss": 0.0,
+ "Duration": 60,
+ "FrameSizeList": 64,
+ "LearningMode": 'AUTO',
+ "NumOfTrials": 1,
+ "RateInitial": 99.0,
+ "RateLowerLimit": 99.0,
+ "RateStep": 10.0,
+ "RateUpperLimit": 99.0,
+ "Resolution": 1.0,
+ "SearchMode": 'BINARY',
+ "TrafficPattern": 'PAIR'
+ }
+
+ def __init__(self, name, lab_server_ip, license_server_ip,
+ west_stcv_admin_ip, west_stcv_tst_ip,
+ east_stcv_admin_ip, east_stcv_tst_ip,
+ stack_id=None, **kwargs):
+ self.logger = logging.getLogger(__name__)
+
+ self.name = name
+ self.lab_server_ip = lab_server_ip
+ self.license_server_ip = license_server_ip
+ self.west_stcv_ip = west_stcv_admin_ip
+ self.west_stcv_tst_ip = west_stcv_tst_ip
+ self.east_stcv_ip = east_stcv_admin_ip
+ self.east_stcv_tst_ip = east_stcv_tst_ip
+ self.stack_id = stack_id
+ self.metric = kwargs.get('metric')
+ self.additional_params = copy.copy(self.default_additional_params)
+ self.additional_params['FrameSizeList'] = kwargs.get('framesizes')
+
+ self.tc_id = str(uuid.uuid4())
+
+ self.stc = None
+ self.sess = None
+ self.executor = None
+ self.status = 'init'
+ self.err_reason = ''
+
+ def config_license(self, license_server_addr):
+ license_mgr = self.stc.get("system1", "children-licenseservermanager")
+ self.stc.create("LicenseServer",
+ under=license_mgr,
+ attributes={"server": license_server_addr})
+ return
+
+ def create_project(self, traffic_custom=None):
+ self.project = self.stc.get("System1", "children-Project")
+ # Configure any custom traffic parameters
+ if traffic_custom == "cont":
+ self.stc.create("ContinuousTestConfig", under=self.project)
+ return
+
+ def config_test_port(self, chassis_addr, slot_no, port_no, intf_addr, gateway_addr):
+ # create test port
+ port_loc = "//%s/%s/%s" % (chassis_addr, slot_no, port_no)
+ chassis_port = self.stc.create('port', self.project)
+ self.stc.config(chassis_port, {'location': port_loc})
+
+ # Create emulated genparam for east port
+ device_gen_params = self.stc.create("EmulatedDeviceGenParams",
+ under=self.project,
+ attributes={"Port": chassis_port})
+ # Create the DeviceGenEthIIIfParams object
+ self.stc.create("DeviceGenEthIIIfParams",
+ under=device_gen_params,
+ attributes={"UseDefaultPhyMac": "True"})
+
+ # Configuring Ipv4 interfaces
+ self.stc.create("DeviceGenIpv4IfParams",
+ under=device_gen_params,
+ attributes={"Addr": intf_addr, "Gateway": gateway_addr})
+
+ # Create Devices using the Device Wizard
+ self.stc.perform("DeviceGenConfigExpand",
+ params={"DeleteExisting": "No", "GenParams": device_gen_params})
+
+ return
+
+ def do_test(self):
+ if self.metric == "throughput":
+ self.stc.perform("Rfc2544SetupThroughputTestCommand", self.additional_params)
+ elif self.metric == "backtoback":
+ self.stc.perform("Rfc2544SetupBackToBackTestCommand", self.additional_params)
+ elif self.metric == "frameloss":
+ self.stc.perform("Rfc2544SetupFrameLossTestCommand", self.additional_params)
+ elif self.metric == "latency":
+ self.stc.perform("Rfc2544SetupLatencyTestCommand", self.additional_params)
+ else:
+ raise Exception("invalid rfc2544 test metric.")
+
+ # Save the configuration
+ self.stc.perform("SaveToTcc", params={"Filename": "2544.tcc"})
+
+ # Connect to the hardware...
+ self.stc.perform("AttachPorts",
+ params={"portList": self.stc.get("system1.project", "children-port"),
+ "autoConnect": "TRUE"})
+
+ # Apply configuration.
+ self.stc.apply()
+ self.stc.perform("SequencerStart")
+ self.stc.wait_until_complete()
+
+ return
+
+ def write_query_results_to_csv(self, results_path, csv_results_file_prefix, query_results):
+ filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
+ with open(filec, "wb") as result_file:
+ result_file.write(query_results["Columns"].replace(" ", ",") + "\n")
+ for row in (query_results["Output"].replace("} {", ",").replace("{", "").replace("}", "").split(",")):
+ result_file.write(row.replace(" ", ",") + "\n")
+
+ def format_result(self, metric, original_result_dict):
+ result = {}
+ if metric == 'throughput':
+ columns = original_result_dict["Columns"].split(' ')
+ index_framesize = columns.index("ConfiguredFrameSize")
+ index_result = columns.index("Result")
+ index_throughput = columns.index("Throughput(%)")
+ index_ForwardingRate = columns.index("ForwardingRate(fps)")
+ outputs = \
+ original_result_dict["Output"].replace('} {', ',').replace("{", "").replace("}", "").split(",")
+
+ for row in outputs:
+ output = row.split(' ')
+ result[output[index_framesize]] = {'Result': output[index_result],
+ "Throughput(%)": output[index_throughput],
+ "ForwardingRate(fps)": output[index_ForwardingRate]}
+
+ elif self.metric == "latency":
+ pass
+
+ elif self.metric == "frameloss":
+ pass
+
+ elif self.metric == "backtoback":
+ pass
+
+ return result
+
+ def collect_result(self, local_dir):
+ # Determine what the results database filename is...
+ lab_server_resultsdb = self.stc.get(
+ "system1.project.TestResultSetting", "CurrentResultFileName")
+ self.stc.perform("CSSynchronizeFiles",
+ params={"DefaultDownloadDir": local_dir})
+
+ resultsdb = local_dir + lab_server_resultsdb.split("/Results")[1]
+
+ if not os.path.exists(resultsdb):
+ resultsdb = lab_server_resultsdb
+ self.logger.info("Failed to create the local summary DB File, using"
+ " the remote DB file instead.")
+ else:
+ self.logger.info(
+ "The local summary DB file has been saved to %s", resultsdb)
+
+ if self.metric == "throughput":
+ resultsdict = self.stc.perform("QueryResult",
+ params={
+ "DatabaseConnectionString": lab_server_resultsdb,
+ "ResultPath": "RFC2544ThroughputTestResultDetailedSummaryView"})
+ elif self.metric == "backtoback":
+ resultsdict = self.stc.perform("QueryResult",
+ params={
+ "DatabaseConnectionString": lab_server_resultsdb,
+ "ResultPath": "RFC2544Back2BackTestResultDetailedSummaryView"})
+ elif self.metric == "frameloss":
+ resultsdict = self.stc.perform("QueryResult",
+ params={
+ "DatabaseConnectionString": lab_server_resultsdb,
+ "ResultPath": "RFC2544LatencyTestResultDetailedSummaryView"})
+ elif self.metric == "latency":
+ resultsdict = self.stc.perform("QueryResult",
+ params={
+ "DatabaseConnectionString": lab_server_resultsdb,
+ "ResultPath": "RFC2544FrameLossTestResultDetailedSummaryView"})
+ else:
+ raise Exception("invalid rfc2544 test metric.")
+
+ self.write_query_results_to_csv(self.results_dir, self.metric, resultsdict)
+
+ self.result = self.format_result(self.metric, resultsdict)
+
+ return
+
+ def thread_entry(self):
+ self.status = self.TC_STATUS_RUNNING
+ try:
+ # create session on lab server
+ self.sess = StcSession(self.lab_server_ip, session_name=self.name, user_name=self.name)
+ self.stc = self.sess.stc
+
+ # create test result directory
+ self.results_dir = self.RESULT_PATH_PREFIX + self.tc_id + '/'
+ os.makedirs(self.results_dir)
+
+ # Bring up license server
+ self.config_license(self.license_server_ip)
+
+ self.logger.info("config license success, license_server_addr = %s.", self.license_server_ip)
+
+ # Create the root project object and Configure any custom traffic parameters
+ self.create_project()
+
+ self.logger.info("create project success.")
+
+ # configure test port
+ self.config_test_port(self.west_stcv_ip, 1, 1, self.west_stcv_tst_ip, self.east_stcv_tst_ip)
+ self.config_test_port(self.east_stcv_ip, 1, 1, self.east_stcv_tst_ip, self.west_stcv_tst_ip)
+
+ self.logger.info("config test port success, west_chassis_addr = %s, east_chassis_addr = %s.",
+ self.west_stcv_ip, self.east_stcv_ip)
+
+ # execute test
+ self.do_test()
+
+ self.logger.info("execute test success.")
+
+ # collect test result
+ self.collect_result(self.results_dir)
+
+ self.logger.info("collect result file success, results_dir = %s.", self.results_dir)
+
+ self.status = self.TC_STATUS_FINISHED
+
+ except Exception as err:
+ self.logger.error("Failed to execute Rfc2544 testcase, err: %s", str(err))
+ self.err_reason = str(err)
+ self.status = self.TC_STATUS_ERROR
+
+ finally:
+ if self.sess is not None:
+ self.sess.clean_all_session()
+
+ def execute(self):
+
+ self.executor = threading.Thread(name='rfc2544', target=self.thread_entry())
+ self.executor.start()
+
+ def get_result(self):
+ if self.status != self.TC_STATUS_FINISHED:
+ return {'name': self.name,
+ 'tc_id': self.tc_id,
+ 'status': self.status
+ }
+
+ return {'name': self.name,
+ 'category': 'rfc2544',
+ 'id': self.tc_id,
+ 'params': {
+ 'metric': self.metric,
+ 'framesizes': self.additional_params.get('FrameSizeList')},
+ 'result': self.result}
+
+ def get_status(self):
+ return self.status
+
+ def delete_result(self):
+ shutil.rmtree(self.results_dir)
+ pass
+
+ def cancel_run(self):
+ pass
+
+ def get_err_reason(self):
+ return self.err_reason
+
+
+if __name__ == '__main__':
+
+ lab_server_ip = '192.168.37.122'
+ license_server_ip = '192.168.37.251'
+ west_stcv_admin_ip = '192.168.37.202'
+ west_stcv_tst_ip = '192.168.1.20'
+ east_stcv_admin_ip = '192.168.37.212'
+ east_stcv_tst_ip = '192.168.1.17'
+
+ tc = StcRfc2544Test(name='tc1',
+ lab_server_ip=lab_server_ip,
+ license_server_ip=license_server_ip,
+ west_stcv_admin_ip=west_stcv_admin_ip,
+ west_stcv_tst_ip=west_stcv_tst_ip,
+ east_stcv_admin_ip=east_stcv_admin_ip,
+ east_stcv_tst_ip=east_stcv_tst_ip,
+ metric="throughput",
+ framesizes=[64, 128, 256, 512, 1024])
+ tc.execute()
+ status = tc.get_status()
+ while(status != tc.TC_STATUS_FINISHED):
+ if status == tc.TC_STATUS_ERROR:
+ print "tc exectue fail, reason %s" % tc.get_err_reason()
+ break
+ sleep(2)
+ if status == tc.TC_STATUS_FINISHED:
+ print tc.get_result()
+'''
+ tc = StcRfc2544Test(name='tc2',
+ lab_server_ip=lab_server_ip,
+ license_server_ip=license_server_ip,
+ west_stcv_admin_ip=west_stcv_admin_ip,
+ west_stcv_tst_ip=west_stcv_tst_ip,
+ east_stcv_admin_ip=east_stcv_admin_ip,
+ east_stcv_tst_ip=east_stcv_tst_ip,
+ metric="latency",
+ framesizes=[64, 128, 256, 512, 1024])
+ tc.execute()
+ status = tc.get_status()
+ while(status != tc.TC_STATUS_FINISHED):
+ if status == tc.TC_STATUS_ERROR:
+ print "tc exectue fail, reason %s" % tc.get_err_reason()
+ break
+ sleep(2)
+ if status == tc.TC_STATUS_FINISHED:
+ print tc.get_result()
+
+ tc = StcRfc2544Test(name='tc3',
+ lab_server_ip=lab_server_ip,
+ license_server_ip=license_server_ip,
+ west_stcv_admin_ip=west_stcv_admin_ip,
+ west_stcv_tst_ip=west_stcv_tst_ip,
+ east_stcv_admin_ip=east_stcv_admin_ip,
+ east_stcv_tst_ip=east_stcv_tst_ip,
+ metric="backtoback",
+ framesizes=[64, 128, 256, 512, 1024])
+ tc.execute()
+ status = tc.get_status()
+ while(status != tc.TC_STATUS_FINISHED):
+ if status == tc.TC_STATUS_ERROR:
+ print "tc exectue fail, reason %s" % tc.get_err_reason()
+ break
+ sleep(2)
+ if status == tc.TC_STATUS_FINISHED:
+ print tc.get_result()
+
+ tc = StcRfc2544Test(name='tc4',
+ lab_server_ip=lab_server_ip,
+ license_server_ip=license_server_ip,
+ west_stcv_admin_ip=west_stcv_admin_ip,
+ west_stcv_tst_ip=west_stcv_tst_ip,
+ east_stcv_admin_ip=east_stcv_admin_ip,
+ east_stcv_tst_ip=east_stcv_tst_ip,
+ metric="frameloss",
+ framesizes=[64, 128, 256, 512, 1024])
+ tc.execute()
+ status = tc.get_status()
+ while(status != tc.TC_STATUS_FINISHED):
+ if status == tc.TC_STATUS_ERROR:
+ print "tc exectue fail, reason %s" % tc.get_err_reason()
+ break
+ sleep(2)
+ if status == tc.TC_STATUS_FINISHED:
+ print tc.get_result()
+'''
+
+'''
+class Testcase(object):
+
+ def __init__(self, stack):
+ self.stack = stack
+
+ def execute(self):
+ pass
+
+class TestcaseFactory(object):
+
+ def __init__(self):
+
+ def create_tc(self, tc_metadata):
+ self.tc_name = tc_metadata['tc_name']
+ self.tc_id = str(uuid.uuid4())
+ if
+'''
diff --git a/contrib/nettest/nettest/start.sh b/contrib/nettest/nettest/start.sh
new file mode 100644
index 00000000..12ae3eb0
--- /dev/null
+++ b/contrib/nettest/nettest/start.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2018 Spirent Communications and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+exec /usr/bin/python rest_server.py
diff --git a/contrib/nettest/nettest/stcv_stack.py b/contrib/nettest/nettest/stcv_stack.py
new file mode 100644
index 00000000..6e69f479
--- /dev/null
+++ b/contrib/nettest/nettest/stcv_stack.py
@@ -0,0 +1,174 @@
+##############################################################################
+# Copyright (c) 2018 Spirent Communications and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import logging
+import os
+from time import sleep
+import traceback
+
+import heatclient.client as heatclient
+from keystoneauth1 import loading
+from keystoneauth1 import session
+
+
+class StcvStack(object):
+ STCV_CONFIG_FILE = 'stcv_config_file'
+ STCV_HEAT_FILE = './heat_2stcv.yaml'
+ STCV_USER_DATA = '''#cloud-config
+ spirent:
+ ntp: '''
+
+ def __init__(self, name, **kwargs):
+ self.logger = logging.getLogger(__name__)
+
+ self.name = name
+ self.pub_net_name = kwargs.get('pub_net_name')
+ self.ntp_server_ip = kwargs.get('ntp_server_ip')
+ self.lab_server_ip = kwargs.get('lab_server_ip')
+ self.stcv_image = kwargs.get('stcv_image')
+ self.stcv_flavor = kwargs.get('stcv_flavor')
+ if kwargs.get('stcv_affinity'):
+ self.stcv_affinity = 'affinity'
+ else:
+ self.stcv_affinity = 'anti-affinity'
+
+ self.stack_id = None
+ self._heatc_lient = None
+
+ def _attach_to_openstack(self):
+ creds = {"username": os.environ.get('OS_USERNAME'),
+ "password": os.environ.get('OS_PASSWORD'),
+ "auth_url": os.environ.get('OS_AUTH_URL'),
+ "project_domain_id": os.environ.get('OS_PROJECT_DOMAIN_ID'),
+ "project_domain_name": os.environ.get('OS_PROJECT_DOMAIN_NAME'),
+ "project_id": os.environ.get('OS_PROJECT_ID'),
+ "project_name": os.environ.get('OS_PROJECT_NAME'),
+ "tenant_name": os.environ.get('OS_TENANT_NAME'),
+ "tenant_id": os.environ.get("OS_TENANT_ID"),
+ "user_domain_id": os.environ.get('OS_USER_DOMAIN_ID'),
+ "user_domain_name": os.environ.get('OS_USER_DOMAIN_NAME')
+ }
+
+ self.logger.debug("Creds: %s" % creds)
+
+ loader = loading.get_plugin_loader('password')
+ auth = loader.load_from_options(**creds)
+ sess = session.Session(auth)
+ self._heat_client = heatclient.Client("1", session=sess)
+
+ def _make_parameters(self):
+ user_data = self.STCV_USER_DATA + self.ntp_server_ip
+ file_path = os.getcwd() + '/' + self.STCV_CONFIG_FILE
+ fd = open(file_path, 'w')
+ fd.writelines(user_data)
+ fd.close()
+
+ return {
+ 'public_net_name': self.pub_net_name,
+ 'stcv_image': self.stcv_image,
+ 'stcv_flavor': self.stcv_flavor,
+ 'stcv_sg_affinity': self.stcv_affinity,
+ 'ntp_server_ip': self.ntp_server_ip
+ }
+
+ def acquire_ip_from_stack_output(self, output, key_name):
+ ip = None
+ for item in output:
+ if item['output_key'] == key_name:
+ ip = item['output_value']
+ if isinstance(ip, list):
+ ip = ip[0]['ip_address']
+ break
+
+ return ip
+
+ def create_stack(self):
+ with open(self.STCV_HEAT_FILE) as fd:
+ template = fd.read()
+
+ self._attach_to_openstack()
+
+ self.logger.debug("Creating stack")
+
+ stack = self._heat_client.stacks.create(
+ stack_name=self.name,
+ template=template,
+ parameters=self._make_parameters())
+
+ self.stack_id = stack['stack']['id']
+
+ while True:
+ stack = self._heat_client.stacks.get(self.stack_id)
+ status = getattr(stack, 'stack_status')
+ self.logger.debug("Stack status=%s" % (status,))
+ if (status == u'CREATE_COMPLETE'):
+ self.stcv1_ip = self.acquire_ip_from_stack_output(stack.outputs, "STCv_1_Mgmt_Ip")
+ self.stcv2_ip = self.acquire_ip_from_stack_output(stack.outputs, "STCv_2_Mgmt_Ip")
+ self.stcv1_tst_ip = self.acquire_ip_from_stack_output(stack.outputs, "STCv_1_Tst_Ip")
+ self.stcv2_tst_ip = self.acquire_ip_from_stack_output(stack.outputs, "STCv_2_Tst_Ip")
+ break
+ if (status == u'DELETE_COMPLETE'):
+ self.stack_id = None
+ break
+ if (status == u'CREATE_FAILED'):
+ self.status_reason = getattr(stack, 'stack_status_reason')
+ sleep(5)
+ self._heat_client.stacks.delete(stack_id=self.stack_id)
+ sleep(2)
+
+ def delete_stack(self):
+ if self.stack_id is None:
+ raise Exception('stack does not exist')
+
+ self._attach_to_openstack()
+ while True:
+ stack = self._heat_client.stacks.get(self.stack_id)
+ status = getattr(stack, 'stack_status')
+ self.logger.debug("Stack status=%s" % (status,))
+ if (status == u'CREATE_COMPLETE'):
+ self._heat_client.stacks.delete(stack_id=self.stack_id)
+ if (status == u'DELETE_COMPLETE'):
+ self.stack_id = None
+ break
+ if (status == u'DELETE_FAILED'):
+ sleep(5)
+ self._heat_client.stacks.delete(stack_id=self.stack_id)
+ sleep(2)
+
+ def get_west_stcv_ip(self):
+ return self.stcv1_ip
+
+ def get_west_stcv_tst_ip(self):
+ return self.stcv1_tst_ip
+
+ def get_east_stcv_ip(self):
+ return self.stcv2_ip
+
+ def get_east_stcv_tst_ip(self):
+ return self.stcv2_tst_ip
+
+
+if __name__ == '__main__':
+ try:
+ stack = StcvStack(name='stack1',
+ pub_net_name='external',
+ ntp_server_ip='192.168.37.151',
+ stcv_image='stcv-4.79',
+ stcv_flavor='m1.tiny',
+ affinity=False)
+ stack.create_stack()
+
+ print stack.get_east_stcv_ip()
+ print stack.get_east_stcv_tst_ip()
+ print stack.get_west_stcv_ip()
+ print stack.get_west_stcv_tst_ip()
+
+ except Exception as err:
+ excstr = traceback.format_exc()
+ print excstr