summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/congress/test-webapp/README.txt189
-rw-r--r--components/congress/test-webapp/www/html/congress_translators.js724
-rw-r--r--components/congress/test-webapp/www/html/copper.css19
-rw-r--r--components/congress/test-webapp/www/html/copper.js261
-rw-r--r--components/congress/test-webapp/www/html/index.html3
-rw-r--r--components/congress/test-webapp/www/html/proxy/index.php1
-rw-r--r--tests/adhoc/dmz01.sh86
-rw-r--r--tests/adhoc/smoke01-clean.sh48
-rw-r--r--tests/adhoc/smoke01.sh58
-rw-r--r--tests/setup/trusty-copper.sh152
10 files changed, 1464 insertions, 77 deletions
diff --git a/components/congress/test-webapp/README.txt b/components/congress/test-webapp/README.txt
index dc3484f..aa0f91c 100644
--- a/components/congress/test-webapp/README.txt
+++ b/components/congress/test-webapp/README.txt
@@ -14,24 +14,185 @@
#
# This file contains instructions for installation and use of the Copper
-# project adhoc test driver for OpenStack Congress.
+# project adhoc test driver for OpenStack Congress, formatted as a
+# Docuwiki page.
-#
-# For Ubuntu
-#
-# install dependencies
+Following are notes on creating a container as test driver for Congress. This is based upon an Ubuntu host as installed by JOID.
+
+=== Create and Activate the Container ===
+
+<code>
+sudo lxc-create -n trusty-copper -t /usr/share/lxc/templates/lxc-ubuntu -- -b ubuntu ~/opnfv
+
+sudo lxc-start -n trusty-copper -d
+
+sudo lxc-info --name trusty-copper
+Name: trusty-copper
+State: RUNNING
+PID: 4563
+IP: 10.0.3.44
+CPU use: 28.77 seconds
+BlkIO use: 522.79 MiB
+Memory use: 559.75 MiB
+KMem use: 0 bytes
+Link: vethDMFOAN
+ TX bytes: 2.62 MiB
+ RX bytes: 88.48 MiB
+ Total bytes: 91.10 MiB
+</code>
+
+=== Login and configure the test server ===
+<code>
+ssh ubuntu@10.0.3.44
+sudo apt-get update
+sudo apt-get upgrade -y
+
+# Install pip
+sudo apt-get install python-pip -y
+
+# Install java
+sudo apt-get install default-jre -y
+
+# Install other dependencies
+sudo apt-get install git gcc python-dev libxml2 libxslt1-dev libzip-dev php5-curl -y
+
+# Setup OpenStack environment variables per your OPNFV install
+export CONGRESS_HOST=192.168.10.117
+export KEYSTONE_HOST=192.168.10.108
+export CEILOMETER_HOST=192.168.10.105
+export CINDER_HOST=192.168.10.101
+export GLANCE_HOST=192.168.10.106
+export HEAT_HOST=192.168.10.107
+export NEUTRON_HOST=192.168.10.111
+export NOVA_HOST=192.168.10.112
+source ~/admin-openrc.sh
+
+# Install and test OpenStack client
+mkdir ~/git
+cd git
+git clone https://github.com/openstack/python-openstackclient.git
+cd python-openstackclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+openstack service list
++----------------------------------+------------+----------------+
+| ID | Name | Type |
++----------------------------------+------------+----------------+
+| 2f8799ae50f24c928c021fabf8a50f5f | keystone | identity |
+| 351b13f56d9a4e25849406ec1d5a2726 | cinder | volume |
+| 5129510c3143454f9ba8ec7e6735e267 | cinderv2 | volumev2 |
+| 5ee1e220460f41dea9be06921400ce9b | congress | policy |
+| 78e73a7789a14f56a5d248a0cd141201 | quantum | network |
+| 9d5a00fb475a45b2ae6767528299ed6b | ceilometer | metering |
+| 9e4b1624ef0b434abc0b82f607c5045c | heat | orchestration |
+| b6c01ceb5023442d9f394b83f2a18e01 | heat-cfn | cloudformation |
+| ba6199e3505045ad87e2a7175bd0c57f | glance | image |
+| d753f304a0d541dbb989780ae70328a8 | nova | compute |
++----------------------------------+------------+----------------+
+
+# Install and test Congress client
+cd ~/git
+git clone https://github.com/openstack/python-congressclient.git
+cd python-congressclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+openstack congress driver list
++------------+--------------------------------------------------------------------------+
+| id | description |
++------------+--------------------------------------------------------------------------+
+| ceilometer | Datasource driver that interfaces with ceilometer. |
+| neutronv2 | Datasource driver that interfaces with OpenStack Networking aka Neutron. |
+| nova | Datasource driver that interfaces with OpenStack Compute aka nova. |
+| keystone | Datasource driver that interfaces with keystone. |
+| cinder | Datasource driver that interfaces with OpenStack cinder. |
+| glancev2 | Datasource driver that interfaces with OpenStack Images aka Glance. |
++------------+--------------------------------------------------------------------------+
+
+# Install and test Glance client
+cd ~/git
+git clone https://github.com/openstack/python-glanceclient.git
+cd python-glanceclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+glance image-list
++--------------------------------------+---------------------+
+| ID | Name |
++--------------------------------------+---------------------+
+| 6ce4433e-65c0-4cd8-958d-b06e30c76241 | cirros-0.3.3-x86_64 |
++--------------------------------------+---------------------+
+
+# Install and test Neutron client
+cd ~/git
+git clone https://github.com/openstack/python-neutronclient.git
+cd python-neutronclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+neutron net-list
++--------------------------------------+------# install dependencies
sudo apt-get install lamp-server^ -y
# when prompted, set mysql root user password to "ubuntu"
-sudo apt-get install php5-curl
+----+------------------------------------------------------+
+| id | name | subnets |
++--------------------------------------+----------+------------------------------------------------------+
+| dc6227df-af41-439f-bd2c-c2c2f0fe7fc5 | public | 5745846c-dd79-4900-a7da-bf506348ceac 192.168.10.0/24 |
+| a3f9f13a-5de9-4d3b-98c8-d2e40a2ef8e9 | internal | 5e0be862-90da-44ab-af43-56d5c65aa049 10.0.0.0/24 |
++--------------------------------------+----------+------------------------------------------------------+
+
+# Install and test Nova client
+cd ~/git
+git clone https://github.com/openstack/python-novaclient.git
+cd python-novaclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+nova hypervisor-list
++----+---------------------+-------+---------+
+| ID | Hypervisor hostname | State | Status |
++----+---------------------+-------+---------+
+| 1 | compute1.maas | up | enabled |
++----+---------------------+-------+---------+
+
+# Install and test Keystone client
+cd ~/git
+git clone https://github.com/openstack/python-keystoneclient.git
+cd python-keystoneclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+
+</code>
+
+=== Setup the Congress Test Webapp ===
-# get Copper test driver app
-cd ~
-git clone https://gerrit.opnfv.org/gerrit/copper
-sudo cp ~copper/components/congress/test-webapp/www/ubuntu-apache2.conf /etc/apache2/apache2.conf
-sudo cp -R ~copper/components/congress/test-webapp/www/html /var/www
+<code>
+# Clone Copper (if not already cloned in user home)
+cd ~/git
+if [ ! -d ~/git/copper ]; then git clone https://gerrit.opnfv.org/gerrit/copper; fi
+
+# Copy the Apache config
+sudo cp ~/git/copper/components/congress/test-webapp/www/ubuntu-apache2.conf /etc/apache2/apache2.conf
+
+# Point proxy.php to the Congress server per your install
+sed -i -- "s/192.168.10.117/$CONGRESS_HOST/g" \
+ ~/git/copper/components/congress/test-webapp/www/html/proxy/index.php
+
+# Copy the webapp to the Apache root directory and fix permissions
+sudo cp -R ~/git/copper/components/congress/test-webapp/www/html /var/www
sudo chmod 755 /var/www/html -R
+
+# Make webapp log directory and set permissions
+mkdir ~/logs
+chmod 777 ~/logs
+
+# Restart Apache
sudo service apache2 restart
+</code>
+
+=== Using the Test Webapp ===
+Browse to the trusty-copper server IP address.
-# Using the app: Browse to http://localhost
-# Interactive options are meant to be self-explanatory given a basic
-# familiarity with the Congress service and data model.
+Interactive options are meant to be self-explanatory given a basic familiarity with the Congress service and data model. But the app will be developed with additional features and UI elements.
diff --git a/components/congress/test-webapp/www/html/congress_translators.js b/components/congress/test-webapp/www/html/congress_translators.js
new file mode 100644
index 0000000..951cbcb
--- /dev/null
+++ b/components/congress/test-webapp/www/html/congress_translators.js
@@ -0,0 +1,724 @@
+/*
+ Copyright 2015 Open Platform for NFV Project, Inc. and its contributors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/* This file contains the translator definition code from the Congress
+ stable/liberty branch. It's been minimally edited for javascript
+ compatibility and will be used by the Copper test webapp to:
+ - present row field names to the user
+ - enable test driver functions as needed, e.g. create/test policies
+
+ The process for conversion of the python to Javascript included:
+ - replace array syntax () with []
+ - add semicolon line endings
+ - change TRANSLATORS to an array
+ - Change "True" to "true"
+*/
+
+var TRANSLATORS = [];
+
+// Stub out this common function until it's clear how it should work
+// in javascript
+/*
+ def safe_id(x):
+ if isinstance(x, six.string_types):
+ return x
+ try:
+ return x['id']
+ except Exception:
+ return str(x)
+*/
+
+ safe_id = function(x) { return(x); };
+
+// nova: from https://raw.githubusercontent.com/openstack/congress/master/congress/datasources/nova_driver.py
+
+ SERVERS = "servers";
+ FLAVORS = "flavors";
+ HOSTS = "hosts";
+ FLOATING_IPS = "floating_IPs";
+ SERVICES = 'services'
+ AVAILABILITY_ZONES = "availability_zones";
+
+ value_trans = {'translation-type': 'VALUE'};
+
+ servers_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': SERVERS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'desc': 'The UUID for the server',
+ 'translator': value_trans},
+ {'fieldname': 'name', 'desc': 'Name of the server',
+ 'translator': value_trans},
+ {'fieldname': 'hostId', 'col': 'host_id',
+ 'desc': 'The UUID for the host', 'translator': value_trans},
+ {'fieldname': 'status', 'desc': 'The server status',
+ 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'desc': 'The tenant ID',
+ 'translator': value_trans},
+ {'fieldname': 'user_id',
+ 'desc': 'The user ID of the user who owns the server',
+ 'translator': value_trans},
+ {'fieldname': 'image', 'col': 'image_id',
+ 'desc': 'Name or ID of image',
+ 'translator': {'translation-type': 'VALUE',
+ 'extract-fn': safe_id}},
+ {'fieldname': 'flavor', 'col': 'flavor_id',
+ 'desc': 'Name of the flavor',
+ 'translator': {'translation-type': 'VALUE',
+ 'extract-fn': safe_id}},
+ {'fieldname': 'OS-EXT-AZ:availability_zone', 'col': 'zone',
+ 'desc': 'The availability zone of host',
+ 'translator': value_trans},
+ {'fieldname': 'OS-EXT-SRV-ATTR:hypervisor_hostname',
+ 'desc': ('The hostname of hypervisor where the server is' +
+ 'running'),
+ 'col': 'host_name', 'translator': value_trans}]};
+
+ flavors_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': FLAVORS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'desc': 'ID of the flavor',
+ 'translator': value_trans},
+ {'fieldname': 'name', 'desc': 'Name of the flavor',
+ 'translator': value_trans},
+ {'fieldname': 'vcpus', 'desc': 'Number of vcpus',
+ 'translator': value_trans},
+ {'fieldname': 'ram', 'desc': 'Memory size in MB',
+ 'translator': value_trans},
+ {'fieldname': 'disk', 'desc': 'Disk size in GB',
+ 'translator': value_trans},
+ {'fieldname': 'ephemeral', 'desc': 'Ephemeral space size in GB',
+ 'translator': value_trans},
+ {'fieldname': 'rxtx_factor', 'desc': 'RX/TX factor',
+ 'translator': value_trans}]};
+
+ hosts_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': HOSTS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'host_name', 'desc': 'Name of host',
+ 'translator': value_trans},
+ {'fieldname': 'service', 'desc': 'Enabled service',
+ 'translator': value_trans},
+ {'fieldname': 'zone', 'desc': 'The availability zone of host',
+ 'translator': value_trans}]};
+
+ floating_ips_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': FLOATING_IPS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'fixed_ip', 'desc': 'Fixed IP Address',
+ 'translator': value_trans},
+ {'fieldname': 'id', 'desc': 'Unique ID',
+ 'translator': value_trans},
+ {'fieldname': 'ip', 'desc': 'IP Address',
+ 'translator': value_trans},
+ {'fieldname': 'instance_id',
+ 'desc': 'Name or ID of host', 'translator': value_trans},
+ {'fieldname': 'pool', 'desc': 'Name of Floating IP Pool',
+ 'translator': value_trans}]};
+
+ services_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': SERVICES,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'col': 'service_id', 'desc': 'Service ID',
+ 'translator': value_trans},
+ {'fieldname': 'binary', 'desc': 'Service binary',
+ 'translator': value_trans},
+ {'fieldname': 'host', 'desc': 'Host Name',
+ 'translator': value_trans},
+ {'fieldname': 'zone', 'desc': 'Availability Zone',
+ 'translator': value_trans},
+ {'fieldname': 'status', 'desc': 'Status of service',
+ 'translator': value_trans},
+ {'fieldname': 'state', 'desc': 'State of service',
+ 'translator': value_trans},
+ {'fieldname': 'updated_at', 'desc': 'Last updated time',
+ 'translator': value_trans},
+ {'fieldname': 'disabled_reason', 'desc': 'Disabled reason',
+ 'translator': value_trans}]};
+
+ availability_zones_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': AVAILABILITY_ZONES,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'zoneName', 'col': 'zone',
+ 'desc': 'Availability zone name', 'translator': value_trans},
+ {'fieldname': 'zoneState', 'col': 'state',
+ 'desc': 'Availability zone state',
+ 'translator': value_trans}]};
+
+ TRANSLATORS["nova"] = [servers_translator, flavors_translator, hosts_translator,
+ floating_ips_translator, services_translator,
+ availability_zones_translator];
+
+// neutronv2: from https://raw.githubusercontent.com/openstack/congress/master/congress/datasources/neutronv2_driver.py
+
+ NETWORKS = 'networks';
+ FIXED_IPS = 'fixed_ips';
+ SECURITY_GROUP_PORT_BINDINGS = 'security_group_port_bindings';
+ PORTS = 'ports';
+ ALLOCATION_POOLS = 'allocation_pools';
+ DNS_NAMESERVERS = 'dns_nameservers';
+ HOST_ROUTES = 'host_routes';
+ SUBNETS = 'subnets';
+ EXTERNAL_FIXED_IPS = 'external_fixed_ips';
+ EXTERNAL_GATEWAY_INFOS = 'external_gateway_infos';
+ ROUTERS = 'routers';
+ SECURITY_GROUP_RULES = 'security_group_rules';
+ SECURITY_GROUPS = 'security_groups';
+ FLOATING_IPS = 'floating_ips';
+
+ value_trans = {'translation-type': 'VALUE'};
+
+ floating_ips_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': FLOATING_IPS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'router_id', 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'translator': value_trans},
+ {'fieldname': 'floating_network_id', 'translator': value_trans},
+ {'fieldname': 'fixed_ip_address', 'translator': value_trans},
+ {'fieldname': 'floating_ip_address', 'translator': value_trans},
+ {'fieldname': 'port_id', 'translator': value_trans},
+ {'fieldname': 'status', 'translator': value_trans}]};
+
+ networks_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': NETWORKS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'status', 'translator': value_trans},
+ {'fieldname': 'admin_state_up', 'translator': value_trans},
+ {'fieldname': 'shared', 'translator': value_trans}]};
+
+ ports_fixed_ips_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': FIXED_IPS,
+ 'parent-key': 'id',
+ 'parent-col-name': 'port_id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'field-translators':
+// TODO: Port ID added to complete table translation
+ [{'fieldname': 'port id', 'translator': value_trans},
+ {'fieldname': 'ip_address', 'translator': value_trans},
+ {'fieldname': 'subnet_id', 'translator': value_trans}]};
+
+ ports_security_groups_translator = {
+// 'translation-type': 'LIST',
+ 'translation-type': 'HDICT',
+ 'table-name': SECURITY_GROUP_PORT_BINDINGS,
+ 'parent-key': 'id',
+ 'parent-col-name': 'port_id',
+ 'val-col': 'security_group_id',
+// 'translator': value_trans};
+// TODO: Port ID added to complete table translation
+ 'field-translators':
+ [{'fieldname': 'port id', 'translator': value_trans},
+ {'fieldname': 'security_group_id', 'translator': value_trans}]};
+
+ ports_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': PORTS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'network_id', 'translator': value_trans},
+ {'fieldname': 'mac_address', 'translator': value_trans},
+ {'fieldname': 'admin_state_up', 'translator': value_trans},
+ {'fieldname': 'status', 'translator': value_trans},
+ {'fieldname': 'device_id', 'translator': value_trans},
+ {'fieldname': 'device_owner', 'translator': value_trans},
+ {'fieldname': 'fixed_ips',
+ 'translator': ports_fixed_ips_translator},
+ {'fieldname': 'security_groups',
+ 'translator': ports_security_groups_translator}]};
+
+ subnets_allocation_pools_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': ALLOCATION_POOLS,
+ 'parent-key': 'id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'field-translators':
+// TODO: ID was missing from the field list
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'start', 'translator': value_trans},
+ {'fieldname': 'end', 'translator': value_trans}]};
+
+ subnets_dns_nameservers_translator = {
+ 'translation-type': 'LIST',
+ 'table-name': DNS_NAMESERVERS,
+ 'parent-key': 'id',
+ 'parent-col-name': 'subnet_id',
+ 'val-col': 'dns_nameserver',
+ 'translator': value_trans};
+
+ subnets_routes_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': HOST_ROUTES,
+ 'parent-key': 'id',
+ 'parent-col-name': 'subnet_id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'field-translators':
+ [{'fieldname': 'destination', 'translator': value_trans},
+ {'fieldname': 'nexthop', 'translator': value_trans}]};
+
+ subnets_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': SUBNETS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'network_id', 'translator': value_trans},
+ {'fieldname': 'ip_version', 'translator': value_trans},
+ {'fieldname': 'cidr', 'translator': value_trans},
+ {'fieldname': 'gateway_ip', 'translator': value_trans},
+ {'fieldname': 'enable_dhcp', 'translator': value_trans},
+ {'fieldname': 'ipv6_ra_mode', 'translator': value_trans},
+ {'fieldname': 'ipv6_address_mode', 'translator': value_trans},
+ {'fieldname': 'allocation_pools',
+ 'translator': subnets_allocation_pools_translator},
+ {'fieldname': 'dns_nameservers',
+ 'translator': subnets_dns_nameservers_translator},
+ {'fieldname': 'host_routes',
+ 'translator': subnets_routes_translator}]};
+
+ external_fixed_ips_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': EXTERNAL_FIXED_IPS,
+ 'parent-key': 'router_id',
+ 'parent-col-name': 'router_id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'field-translators':
+ [{'fieldname': 'subnet_id', 'translator': value_trans},
+ {'fieldname': 'ip_address', 'translator': value_trans}]};
+
+ routers_external_gateway_infos_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': EXTERNAL_GATEWAY_INFOS,
+ 'parent-key': 'id',
+ 'parent-col-name': 'router_id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'network_id', 'translator': value_trans},
+ {'fieldname': 'enable_snat', 'translator': value_trans},
+ {'fieldname': 'external_fixed_ips',
+ 'translator': external_fixed_ips_translator}]};
+
+ routers_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': ROUTERS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'translator': value_trans},
+ {'fieldname': 'status', 'translator': value_trans},
+ {'fieldname': 'admin_state_up', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'distributed', 'translator': value_trans},
+ {'fieldname': 'external_gateway_info',
+ 'translator': routers_external_gateway_infos_translator}]};
+
+ security_group_rules_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': SECURITY_GROUP_RULES,
+ 'parent-key': 'id',
+ 'parent-col-name': 'security_group_id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'field-translators':
+// TODO: Port ID added to complete table translation
+ [{'fieldname': 'port id', 'translator': value_trans},
+ {'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'translator': value_trans},
+ {'fieldname': 'remote_group_id', 'translator': value_trans},
+ {'fieldname': 'direction', 'translator': value_trans},
+ {'fieldname': 'ethertype', 'translator': value_trans},
+ {'fieldname': 'protocol', 'translator': value_trans},
+ {'fieldname': 'port_range_min', 'translator': value_trans},
+ {'fieldname': 'port_range_max', 'translator': value_trans},
+ {'fieldname': 'remote_ip_prefix', 'translator': value_trans}]};
+
+ security_group_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': SECURITY_GROUPS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'tenant_id', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'description', 'translator': value_trans},
+ {'fieldname': 'security_group_rules',
+ 'translator': security_group_rules_translator}]};
+
+// TODO: Some translators were missing from the list
+ TRANSLATORS["neutronv2"] = [floating_ips_translator, networks_translator, ports_fixed_ips_translator, ports_security_groups_translator, ports_translator, subnets_allocation_pools_translator, subnets_dns_nameservers_translator, subnets_routes_translator, subnets_translator, external_fixed_ips_translator, routers_external_gateway_infos_translator, routers_translator, security_group_rules_translator, security_group_translator];
+
+// keystone: from https://raw.githubusercontent.com/openstack/congress/master/congress/datasources/keystone_driver.py
+
+ USERS = "users";
+ ROLES = "roles";
+ TENANTS = "tenants";
+
+ value_trans = {'translation-type': 'VALUE'};
+
+ users_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': USERS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'username', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'enabled', 'translator': value_trans},
+ {'fieldname': 'tenantId', 'translator': value_trans},
+ {'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'email', 'translator': value_trans}]};
+
+ roles_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': ROLES,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans}]};
+
+ tenants_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': TENANTS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'enabled', 'translator': value_trans},
+ {'fieldname': 'description', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'id', 'translator': value_trans}]};
+
+ TRANSLATORS["keystone"] = [users_translator, roles_translator, tenants_translator];
+
+// heat: from https://raw.githubusercontent.com/openstack/congress/master/congress/datasources/heatv1_driver.py
+
+ STACKS = "stacks";
+ STACKS_LINKS = "stacks_links";
+ DEPLOYMENTS = "deployments"; STACKS = "stacks";
+ STACKS_LINKS = "stacks_links";
+ DEPLOYMENTS = "deployments";
+ DEPLOYMENT_OUTPUT_VALUES = "deployment_output_values";
+
+// TODO(thinrichs): add resources, events, snapshots
+
+ value_trans = {'translation-type': 'VALUE'};
+ stacks_links_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': STACKS_LINKS,
+ 'parent-key': 'id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'field-translators':
+ [{'fieldname': 'href', 'translator': value_trans},
+ {'fieldname': 'rel', 'translator': value_trans}]};
+
+ stacks_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': STACKS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'stack_name', 'translator': value_trans},
+ {'fieldname': 'description', 'translator': value_trans},
+ {'fieldname': 'creation_time', 'translator': value_trans},
+ {'fieldname': 'updated_time', 'translator': value_trans},
+ {'fieldname': 'stack_status', 'translator': value_trans},
+ {'fieldname': 'stack_status_reason', 'translator': value_trans},
+ {'fieldname': 'stack_owner', 'translator': value_trans},
+ {'fieldname': 'parent', 'translator': value_trans},
+ {'fieldname': 'links', 'translator': stacks_links_translator}]};
+
+ deployments_output_values_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': DEPLOYMENT_OUTPUT_VALUES,
+ 'parent-key': 'id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'deploy_stdout', 'translator': value_trans},
+ {'fieldname': 'deploy_stderr', 'translator': value_trans},
+ {'fieldname': 'deploy_status_code', 'translator': value_trans},
+ {'fieldname': 'result', 'translator': value_trans}]};
+
+ software_deployment_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': DEPLOYMENTS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'status', 'translator': value_trans},
+ {'fieldname': 'server_id', 'translator': value_trans},
+ {'fieldname': 'config_id', 'translator': value_trans},
+ {'fieldname': 'action', 'translator': value_trans},
+ {'fieldname': 'status_reason', 'translator': value_trans},
+ {'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'output_values',
+ 'translator': deployments_output_values_translator}]};
+
+ TRANSLATORS["heat"] = [stacks_translator, software_deployment_translator];
+ DEPLOYMENT_OUTPUT_VALUES = "deployment_output_values";
+
+/* TODO(thinrichs): add resources, events, snapshots
+*/
+ value_trans = {'translation-type': 'VALUE'};
+ stacks_links_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': STACKS_LINKS,
+ 'parent-key': 'id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'field-translators':
+ [{'fieldname': 'href', 'translator': value_trans},
+ {'fieldname': 'rel', 'translator': value_trans}]};
+
+ stacks_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': STACKS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'stack_name', 'translator': value_trans},
+ {'fieldname': 'description', 'translator': value_trans},
+ {'fieldname': 'creation_time', 'translator': value_trans},
+ {'fieldname': 'updated_time', 'translator': value_trans},
+ {'fieldname': 'stack_status', 'translator': value_trans},
+ {'fieldname': 'stack_status_reason', 'translator': value_trans},
+ {'fieldname': 'stack_owner', 'translator': value_trans},
+ {'fieldname': 'parent', 'translator': value_trans},
+ {'fieldname': 'links', 'translator': stacks_links_translator}]};
+
+ deployments_output_values_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': DEPLOYMENT_OUTPUT_VALUES,
+ 'parent-key': 'id',
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'deploy_stdout', 'translator': value_trans},
+ {'fieldname': 'deploy_stderr', 'translator': value_trans},
+ {'fieldname': 'deploy_status_code', 'translator': value_trans},
+ {'fieldname': 'result', 'translator': value_trans}]};
+
+ software_deployment_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': DEPLOYMENTS,
+ 'selector-type': 'DOT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'status', 'translator': value_trans},
+ {'fieldname': 'server_id', 'translator': value_trans},
+ {'fieldname': 'config_id', 'translator': value_trans},
+ {'fieldname': 'action', 'translator': value_trans},
+ {'fieldname': 'status_reason', 'translator': value_trans},
+ {'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'output_values',
+ 'translator': deployments_output_values_translator}]};
+
+ TRANSLATORS["heat"] = [stacks_translator, software_deployment_translator];
+
+// glancev2: from https://raw.githubusercontent.com/openstack/congress/master/congress/datasources/glancev2_driver.py
+
+ IMAGES = "images";
+ TAGS = "tags";
+
+ value_trans = {'translation-type': 'VALUE'};
+ images_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': IMAGES,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'id', 'translator': value_trans},
+ {'fieldname': 'status', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'container_format', 'translator': value_trans},
+ {'fieldname': 'created_at', 'translator': value_trans},
+ {'fieldname': 'updated_at', 'translator': value_trans},
+ {'fieldname': 'disk_format', 'translator': value_trans},
+ {'fieldname': 'owner', 'translator': value_trans},
+ {'fieldname': 'protected', 'translator': value_trans},
+ {'fieldname': 'min_ram', 'translator': value_trans},
+ {'fieldname': 'min_disk', 'translator': value_trans},
+ {'fieldname': 'checksum', 'translator': value_trans},
+ {'fieldname': 'size', 'translator': value_trans},
+ {'fieldname': 'file', 'translator': value_trans},
+ {'fieldname': 'kernel_id', 'translator': value_trans},
+ {'fieldname': 'ramdisk_id', 'translator': value_trans},
+ {'fieldname': 'schema', 'translator': value_trans},
+ {'fieldname': 'visibility', 'translator': value_trans},
+ {'fieldname': 'tags',
+ 'translator': {'translation-type': 'LIST',
+ 'table-name': TAGS,
+ 'val-col': 'tag',
+ 'parent-key': 'id',
+ 'parent-col-name': 'image_id',
+ 'translator': value_trans}}]};
+
+ TRANSLATORS["glancev2"] = [images_translator];
+
+// ceilometer: from https://raw.githubusercontent.com/openstack/congress/master/congress/datasources/ceilometer_driver.py
+
+ METERS = "meters";
+ ALARMS = "alarms";
+ EVENTS = "events";
+ EVENT_TRAITS = "events.traits";
+ ALARM_THRESHOLD_RULE = "alarms.threshold_rule";
+ STATISTICS = "statistics";
+
+ value_trans = {'translation-type': 'VALUE'};
+
+ meters_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': METERS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'meter_id', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'type', 'translator': value_trans},
+ {'fieldname': 'unit', 'translator': value_trans},
+ {'fieldname': 'source', 'translator': value_trans},
+ {'fieldname': 'resource_id', 'translator': value_trans},
+ {'fieldname': 'user_id', 'translator': value_trans},
+ {'fieldname': 'project_id', 'translator': value_trans}]};
+
+ alarms_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': ALARMS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'alarm_id', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'state', 'translator': value_trans},
+ {'fieldname': 'enabled', 'translator': value_trans},
+ {'fieldname': 'threshold_rule', 'col': 'threshold_rule_id',
+ 'translator': {'translation-type': 'VDICT',
+ 'table-name': ALARM_THRESHOLD_RULE,
+ 'id-col': 'threshold_rule_id',
+ 'key-col': 'key', 'val-col': 'value',
+ 'translator': value_trans}},
+ {'fieldname': 'type', 'translator': value_trans},
+ {'fieldname': 'description', 'translator': value_trans},
+ {'fieldname': 'time_constraints', 'translator': value_trans},
+ {'fieldname': 'user_id', 'translator': value_trans},
+ {'fieldname': 'project_id', 'translator': value_trans},
+ {'fieldname': 'alarm_actions', 'translator': value_trans},
+ {'fieldname': 'ok_actions', 'translator': value_trans},
+ {'fieldname': 'insufficient_data_actions', 'translator':
+ value_trans},
+ {'fieldname': 'repeat_actions', 'translator': value_trans},
+ {'fieldname': 'timestamp', 'translator': value_trans},
+ {'fieldname': 'state_timestamp', 'translator': value_trans},
+ ]};
+
+ events_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': EVENTS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'message_id', 'translator': value_trans},
+ {'fieldname': 'event_type', 'translator': value_trans},
+ {'fieldname': 'generated', 'translator': value_trans},
+ {'fieldname': 'traits',
+ 'translator': {'translation-type': 'HDICT',
+ 'table-name': EVENT_TRAITS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'in-list': true,
+ 'parent-key': 'message_id',
+ 'parent-col-name': 'event_message_id',
+ 'field-translators':
+ [{'fieldname': 'name',
+ 'translator': value_trans},
+ {'fieldname': 'type',
+ 'translator': value_trans},
+ {'fieldname': 'value',
+ 'translator': value_trans}
+ ]}}
+ ]};
+
+ statistics_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': STATISTICS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'meter_name', 'translator': value_trans},
+ {'fieldname': 'groupby', 'col': 'resource_id',
+ 'translator': {'translation-type': 'VALUE',
+ 'extract-fn': safe_id}},
+ {'fieldname': 'avg', 'translator': value_trans},
+ {'fieldname': 'count', 'translator': value_trans},
+ {'fieldname': 'duration', 'translator': value_trans},
+ {'fieldname': 'duration_start', 'translator': value_trans},
+ {'fieldname': 'duration_end', 'translator': value_trans},
+ {'fieldname': 'max', 'translator': value_trans},
+ {'fieldname': 'min', 'translator': value_trans},
+ {'fieldname': 'period', 'translator': value_trans},
+ {'fieldname': 'period_end', 'translator': value_trans},
+ {'fieldname': 'period_start', 'translator': value_trans},
+ {'fieldname': 'sum', 'translator': value_trans},
+ {'fieldname': 'unit', 'translator': value_trans}]};
+
+ TRANSLATORS["ceilometer"] = [meters_translator, alarms_translator, events_translator,
+ statistics_translator];
+
+// swift: from https://raw.githubusercontent.com/openstack/congress/master/congress/datasources/swift_driver.py
+
+ CONTAINERS = "containers";
+ OBJECTS = "objects";
+
+ value_trans = {'translation-type': 'VALUE'};
+
+ containers_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': CONTAINERS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'count', 'translator': value_trans},
+ {'fieldname': 'bytes', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans}]};
+
+ objects_translator = {
+ 'translation-type': 'HDICT',
+ 'table-name': OBJECTS,
+ 'selector-type': 'DICT_SELECTOR',
+ 'field-translators':
+ [{'fieldname': 'bytes', 'translator': value_trans},
+ {'fieldname': 'last_modified', 'translator': value_trans},
+ {'fieldname': 'hash', 'translator': value_trans},
+ {'fieldname': 'name', 'translator': value_trans},
+ {'fieldname': 'content_type', 'translator': value_trans},
+ {'fieldname': 'container_name', 'translator': value_trans}]};
+
+ TRANSLATORS["swift"] = [containers_translator, objects_translator];
diff --git a/components/congress/test-webapp/www/html/copper.css b/components/congress/test-webapp/www/html/copper.css
index 017bd89..51352d6 100644
--- a/components/congress/test-webapp/www/html/copper.css
+++ b/components/congress/test-webapp/www/html/copper.css
@@ -1,14 +1,21 @@
body { font-family: sans-serif; }
-@media screen and (min-width: 200px) { body { font-size:100%; } }
-@media screen and (min-width: 400px) { body { font-size:120%; } }
-@media screen and (min-width: 600px) { body { font-size:150%; } }
p { font-size: 100%; }
span { font-size: inherit; }
-table { font-size: inherit; }
-td { font-size: inherit; }
input { font-size: inherit; display: table;}
-td { font-size: inherit; }
+table { font-size: inherit; border: 1px solid darkslategray; border-collapse: collapse;
+ max-width: 100%;
+ table-layout: fixed;
+ width: 100%;
+ white-space: nowrap; }
+th { border: 1px solid darkslategray; border-collapse: collapse; overflow: hidden;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis; }
+td { border: 1px solid darkslategray; border-collapse: collapse; overflow: hidden;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis; }
button { font-size: inherit; color: white; background-color:darkslategray;
border-style:none; border-radius:10px; -moz-border-radius:10px; -webkit-border-radius:10px;
margin-top:5px; margin-bottom:5px;}
diff --git a/components/congress/test-webapp/www/html/copper.js b/components/congress/test-webapp/www/html/copper.js
index 1d5d3e1..ddc395e 100644
--- a/components/congress/test-webapp/www/html/copper.js
+++ b/components/congress/test-webapp/www/html/copper.js
@@ -15,10 +15,13 @@
*/
var origin = "http://localhost/proxy/?~url=";
var dataSources = [];
-var tables = [];
-var rows = [];
+var datasource_tables = [];
+var datasource_rows = [];
var policies = [];
-var rules = [];
+var policy_tables = [];
+var policy_rows = [];
+var policy_rules = [];
+var translators = [];
function get_dataSources() {
dse = document.getElementById('dataSources');
@@ -29,61 +32,69 @@ function get_dataSources() {
var str = '';
for (i in dataSources) {
datasource = dataSources[i].name;
- dhe = element('button',datasource,datasource,1);
- de = element('div',datasource+":datasource","",1);
+ dhe = element('button',"datasources:"+datasource,datasource,1);
+ de = element('div',"datasource:"+datasource,"",1);
dhe.setAttribute('onclick','toggle("'+de.id+'");');
dse.appendChild(dhe);
de.style.display = 'none'
for (j in dataSources[i]) {
if (typeof dataSources[i][j] == 'object' && dataSources[i][j] != null) {
oe = element('button',"",j,2);
- ae = element('p',datasource+':'+j,null,2);
+ ae = element('table',"datasource:"+datasource+':'+j,null,2);
ae.style.display = 'none'
oe.setAttribute('onclick','toggle("'+ae.id+'");');
de.appendChild(oe);
+ ah = element('thead',"",null,null);
+ ar = element('tr',"",null,null);
for (k in dataSources[i][j]) {
- sae = element('p',"",k+':'+dataSources[i][j][k],3);
- ae.appendChild(sae);
+ ahe = element('th',"",k,3);
+ ah.appendChild(ahe);
+ are = element('td',"",dataSources[i][j][k],3);
+ ar.appendChild(are);
}
+ ae.appendChild(ah);
+ ae.appendChild(ar);
}
else ae = element('p',"",j+':'+dataSources[i][j],2);
de.appendChild(ae);
}
- tb = element('button',"",'Tables',2);
- tb.setAttribute('onclick','get_tables('+i+');');
- de.appendChild(tb);
- tb = element('p',datasource+':tables',"",2);
- de.appendChild(tb);
+ dsb = element('button',"",'Tables',2);
+ dsb.setAttribute('onclick','get_datasource_tables('+i+');');
+ de.appendChild(dsb);
+ dst = element('p',"datasource:"+datasource+':tables',"",2);
+ de.appendChild(dst);
dse.appendChild(de);
}
},null,null,null);
}
-function get_tables(dsIndex) {
+function get_datasource_tables(dsIndex) {
dsid = dataSources[dsIndex].id;
datasource = dataSources[dsIndex].name;
- tb = document.getElementById(datasource+':tables');
+ tb = document.getElementById("datasource:"+datasource+":tables");
if (tb.innerHTML != "" && tb.style.display != 'none') tb.style.display = 'none';
else {
while (tb.firstChild) tb.removeChild(tb.firstChild);
asyncXHR('GET',origin+'/v1/data-sources/'+dsid+'/tables',function(xhr) {
obj = JSON.parse(xhr.responseText);
if (obj.results.length == 0) {
- tables[dsIndex] = [];
- rows[dsIndex] = [];
+ datasource_tables[dsIndex] = [];
+ datasource_rows[dsIndex] = [];
tb.innerHTML = "No tables defined.";
}
else {
- tables[dsIndex] = obj.results;
- rows[dsIndex] = [];
- for (i in tables[dsIndex]) {
- rows[dsIndex][i] = [];
- tid = tables[dsIndex][i].id;
- te = element('button',tid,tid,3);
- te.setAttribute('onclick','get_rows('+dsIndex+','+i+');');
- tb.appendChild(te);
- te = element('div',datasource+":"+tid,"",2);
- tb.appendChild(te);
+ datasource_tables[dsIndex] = obj.results;
+ datasource_rows[dsIndex] = [];
+ for (i in datasource_tables[dsIndex]) {
+ datasource_rows[dsIndex][i] = [];
+ tid = datasource_tables[dsIndex][i].id;
+ tbb = element('button',"datasource:"+datasource+":tables:"+tid+":get",tid,3);
+ tbb.setAttribute('onclick','get_datasource_table_rows('+dsIndex+','+i+');');
+ tb.appendChild(tbb);
+ tbnr = element('span',"datasource:"+datasource+":tables:"+tid+":numrows","",null);
+ tb.appendChild(tbnr);
+ tbd = element('div',"datasource:"+datasource+":tables:"+tid,"",2);
+ tb.appendChild(tbd);
}
}
tb.style.display = 'block';
@@ -91,29 +102,62 @@ function get_tables(dsIndex) {
}
}
-function get_rows(dsIndex,tableIndex) {
+function get_datasource_table_rows(dsIndex,tableIndex) {
datasource = dataSources[dsIndex].name;
dsid = dataSources[dsIndex].id;
- tid = tables[dsIndex][tableIndex].id;
- tb = document.getElementById(datasource+":"+tid);
- if (tb.innerHTML != "" && tb.style.display != 'none') tb.style.display = 'none';
+ tid = datasource_tables[dsIndex][tableIndex].id;
+ tbd = document.getElementById("datasource:"+datasource+":tables:"+tid);
+ if (tbd.innerHTML != "" && tbd.style.display != 'none') tbd.style.display = 'none';
else {
- while (tb.firstChild) tb.removeChild(tb.firstChild);
+ while (tbd.firstChild) tbd.removeChild(tbd.firstChild);
asyncXHR('GET',origin+'/v1/data-sources/'+dsid+'/tables/'+tid+'/rows',function(xhr) {
obj = JSON.parse(xhr.responseText);
- if (obj.results.length == 0) {
- rows[dsIndex][tableIndex] = [];
- tb.innerHTML = "No rows defined.";
+ if (obj.results.length == 0) datasource_rows[dsIndex][tableIndex] = [];
+ else datasource_rows[dsIndex][tableIndex] = obj.results;
+
+ tbnr = document.getElementById("datasource:"+datasource+":tables:"+tid+":numrows");
+ tbnr.innerHTML = " ("+obj.results.length+" rows)<br/>";
+
+ tbr = element('table',"",null,0);
+ tbh = element('thead',"",null,null);
+ for (i in TRANSLATORS[datasource]) {
+ if (tid == TRANSLATORS[datasource][i]['table-name']) {
+ tbhr = element('tr',"",null,null);
+ if (TRANSLATORS[datasource][i]['translation-type'] == 'LIST') {
+ for (j in datasource_rows[dsIndex][tableIndex][0].data) {
+ tbhd = element('th',"",null,null);
+ tbhd.innerHTML = TRANSLATORS[datasource][i]['val-col'];
+ tbhr.appendChild(tbhd);
+ }
+ }
+ else {
+ for (j in TRANSLATORS[datasource][i]['field-translators']) {
+ tbhd = element('th',"",null,null);
+ tbhd.innerHTML = TRANSLATORS[datasource][i]['field-translators'][j]['fieldname'];
+ tbhd.title = TRANSLATORS[datasource][i]['field-translators'][j]['fieldname'];
+ tbhr.appendChild(tbhd);
+ }
+ }
+ tbh.appendChild(tbhr);
+ }
+ tbr.appendChild(tbh);
}
- else {
- rows[dsIndex][tableIndex] = obj.results;
- for (i in rows[dsIndex][tableIndex]) {
- data = rows[dsIndex][tableIndex][i].data;
- te = element('p',"",JSON.stringify(data),3);
- tb.appendChild(te);
+ if (obj.results.length > 0) {
+ datasource_rows[dsIndex][tableIndex] = obj.results;
+ for (i in datasource_rows[dsIndex][tableIndex]) {
+ tbrr = element('tr',"",null,null);
+ data = datasource_rows[dsIndex][tableIndex][i].data;
+ for (j in data) {
+ tbrd = element('td',"",null,null);
+ tbrd.innerHTML = data[j];
+ tbrd.title = data[j];
+ tbrr.appendChild(tbrd);
+ }
+ tbr.appendChild(tbrr);
}
+ tbd.appendChild(tbr);
}
- tb.style.display = 'block';
+ tbd.style.display = 'block';
},null,null,null);
}
}
@@ -125,9 +169,9 @@ function get_policies() {
obj = JSON.parse(xhr.responseText);
policies = obj.results;
for (i in policies) {
- name = policies[i].name;
- he = element('button',"",name,1);
- pe = element('div',name+":policies","",1);
+ policy = policies[i].name;
+ he = element('button',"",policy,1);
+ pe = element('div',"policies:"+policy,"",1);
he.setAttribute('onclick','toggle("'+pe.id+'");');
poe.appendChild(he);
pe.style.display = 'none'
@@ -138,8 +182,13 @@ function get_policies() {
rb = element('button',"",'Rules',2);
rb.setAttribute('onclick','get_rules('+i+');');
pe.appendChild(rb);
- pr = element('p',name+':rules',"",2);
+ pr = element('p',policy+':rules',"",2);
pe.appendChild(pr);
+ pob = element('button',"",'Tables',2);
+ pob.setAttribute('onclick','get_policy_tables('+i+');');
+ pe.appendChild(pob);
+ pot = element('p',"policies:"+policy+':tables',"",2);
+ pe.appendChild(pot);
poe.appendChild(pe);
// {"kind":"nonrecursive","description":"default action policy","name":"action","abbreviation":"actio",
// "id":"29196084-604d-4964-93e6-c23eb2c52990","owner_id":"user"}
@@ -159,7 +208,107 @@ function get_policies() {
}
]
}
- */
+ */http://congress.readthedocs.org/en/latest/api.html
+
+function get_policy_tables(policyIndex) {
+ pid = policies[policyIndex].id;
+ policy = policies[policyIndex].name;
+ tb = document.getElementById("policies:"+policy+':tables');
+ if (tb.innerHTML != "" && tb.style.display != 'none') tb.style.display = 'none';
+ else {
+ while (tb.firstChild) tb.removeChild(tb.firstChild);
+// TODO: verify why http://congress.readthedocs.org/en/latest/api.html uses policy name
+ asyncXHR('GET',origin+'/v1/policies/'+policy+'/tables',function(xhr) {
+ obj = JSON.parse(xhr.responseText);
+ if (obj.results.length == 0) {
+ policy_tables[policyIndex] = [];
+ policy_rows[policyIndex] = [];
+ tb.innerHTML = "No tables defined.";
+ }
+ else {
+ policy_tables[policyIndex] = obj.results;
+ policy_rows[policyIndex] = [];
+ for (i in policy_tables[policyIndex]) {
+ policy_rows[policyIndex][i] = [];
+ tid = policy_tables[policyIndex][i].id;
+ tbb = element('button',"policies:"+policy+":tables:"+tid+":get",tid,3);
+ tbb.setAttribute('onclick','get_policy_table_rows('+policyIndex+','+i+');');
+ tb.appendChild(tbb);
+ tbnr = element('span',"policies:"+policy+":tables:"+tid+":numrows","",null);
+ tb.appendChild(tbnr);
+ tbd = element('div',"policies:"+policy+":tables:"+tid,"",2);
+ tb.appendChild(tbd);
+ }
+ }
+ tb.style.display = 'block';
+ },null,null,null);
+ }
+}
+
+function get_policy_table_rows(policyIndex,tableIndex) {
+ policy = policies[policyIndex].name;
+ pid = policies[policyIndex].id;
+ tid = policy_tables[policyIndex][tableIndex].id;
+ name = policy_tables[policyIndex][tableIndex].name;
+ tbd = document.getElementById("policies:"+policy+":tables:"+tid);
+ if (tbd.innerHTML != "" && tbd.style.display != 'none') tbd.style.display = 'none';
+ else {
+ while (tbd.firstChild) tbd.removeChild(tbd.firstChild);
+// TODO: Verify why policy name is used instead of policy ID
+ asyncXHR('GET',origin+'/v1/policies/'+policy+'/tables/'+tid+'/rows',function(xhr) {
+ obj = JSON.parse(xhr.responseText);
+ if (obj.results.length == 0) policy_rows[policyIndex][tableIndex] = [];
+ else policy_rows[policyIndex][tableIndex] = obj.results;
+
+ tbnr = document.getElementById("policies:"+policy+":tables:"+tid+":numrows");
+ tbnr.innerHTML = " ("+obj.results.length+" rows)<br/>";
+ tbr = element('table',"",null,0);
+ tbh = element('thead',"",null,null);
+ tbr.appendChild(tbh);
+/*
+ tbh = element('thead',"",null,null);
+ for (i in TRANSLATORS[policy]) {
+ if (tid == TRANSLATORS[policy][i]['table-name']) {
+ tbr = element('tr',"",null,null);
+ if (TRANSLATORS[policy][i]['translation-type'] == 'LIST') {
+ for (j in policy_rows[policyIndex][tableIndex][0].data) {
+ tbd = element('th',"",null,null);
+ tbd.innerHTML = TRANSLATORS[policy][i]['val-col'];
+ tbr.appendChild(tbd);
+ }
+ }
+ else {
+ for (j in TRANSLATORS[policy][i]['field-translators']) {
+ tbd = element('th',"",null,null);
+ tbd.title = TRANSLATORS[policy][i]['field-translators'][j]['fieldname'];
+ tbd.innerHTML = TRANSLATORS[policy][i]['field-translators'][j]['fieldname'];
+ tbr.appendChild(tbd);
+ }
+ }
+ tbh.appendChild(tbr);
+ }
+ tbe.appendChild(tbh);
+ }
+*/
+ if (obj.results.length > 0) {
+ policy_rows[policyIndex][tableIndex] = obj.results;
+ for (i in policy_rows[policyIndex][tableIndex]) {
+ tbrr = element('tr',"",null,null);
+ data = policy_rows[policyIndex][tableIndex][i].data;
+ for (j in data) {
+ tbrd = element('td',"",null,null);
+ tbrd.innerHTML = data[j];
+ tbrr.appendChild(tbrd);
+ }
+ tbr.appendChild(tbrr);
+ }
+ tbd.appendChild(tbr);
+ }
+ tbd.style.display = 'block';
+ },null,null,null);
+ }
+}
+
function get_rules(policyIndex) {
policy = policies[policyIndex].name;
pid = policies[policyIndex].id;
@@ -167,16 +316,16 @@ function get_rules(policyIndex) {
while (pr.firstChild) pr.removeChild(pr.firstChild);
asyncXHR('GET',origin+'/v1/policies/'+policy+'/rules',function(xhr) {
obj = JSON.parse(xhr.responseText);
- rules[policyIndex] = obj.results;
+ policy_rules[policyIndex] = obj.results;
if (obj.results.length == 0) {
- rules[policyIndex] = [];
+ policy_rules[policyIndex] = [];
pr.innerHTML = "No rules defined.";
}
else {
var str = '';
-// alert(JSON.stringify(rules[policyIndex]));
- for (i in rules[policyIndex]) {
- name = rules[policyIndex][i].name;
+// alert(JSON.stringify(policy_rules[policyIndex]));
+ for (i in policy_rules[policyIndex]) {
+ name = policy_rules[policyIndex][i].name;
he = element('button',name,name,3);
re = element('div',name+"json","",4);
he.setAttribute('onclick','toggle("'+re.id+'");');
@@ -185,8 +334,8 @@ function get_rules(policyIndex) {
de.setAttribute('onclick','delete_rule('+policyIndex+','+i+');');
pr.appendChild(de);
re.style.display = 'none';
- for (j in rules[policyIndex][i]) {
- ae = element('p',"",j+':'+rules[policyIndex][i][j],4);
+ for (j in policy_rules[policyIndex][i]) {
+ ae = element('p',"",j+':'+policy_rules[policyIndex][i][j],4);
re.appendChild(ae);
}
pr.appendChild(re);
@@ -236,8 +385,8 @@ function create_rule(policyIndex,name,comment,rule) {
*/
function delete_rule(policyIndex,ruleIndex) {
policy = policies[policyIndex].name;
- name = rules[policyIndex][ruleIndex].name;
- id = rules[policyIndex][ruleIndex].id;
+ name = policy_rules[policyIndex][ruleIndex].name;
+ id = policy_rules[policyIndex][ruleIndex].id;
// use policy name rather than id as the id!
asyncXHR('DELETE',origin+'/v1/policies/'+policy+'/rules/'+id,function(xhr) {
// BUG: Congress creates rules asyncchronously, thus a query for rules immediately after rule creation may not return the newly created rule
diff --git a/components/congress/test-webapp/www/html/index.html b/components/congress/test-webapp/www/html/index.html
index a36bcd5..4d0164d 100644
--- a/components/congress/test-webapp/www/html/index.html
+++ b/components/congress/test-webapp/www/html/index.html
@@ -4,7 +4,7 @@
<title>OPNFV Congress Test</title>
<link rel='stylesheet' type='text/css' href='copper.css'/>
<!--
- Copyright (c) 2015 AT&T Intellectual Property
+ Copyright 2015 Open Platform for NFV Project, Inc. and its contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
limitations under the License.
-->
<script type='text/javascript' src='copper.js'></script>
+<script type='text/javascript' src='congress_translators.js'></script>
<meta name='viewport' content='width=device-width, minimum-scale=1.0, initial-scale=1.0 user-scalable=0'/>
</head>
<body>
diff --git a/components/congress/test-webapp/www/html/proxy/index.php b/components/congress/test-webapp/www/html/proxy/index.php
index 93be444..1ce4e45 100644
--- a/components/congress/test-webapp/www/html/proxy/index.php
+++ b/components/congress/test-webapp/www/html/proxy/index.php
@@ -18,6 +18,7 @@ $method = $_SERVER['REQUEST_METHOD'];
if ($method == 'OPTIONS') {
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
+ header("Access-Control-Allow-Headers: Content-Type");
header("Access-Control-Allow-Methods: GET, POST, DELETE");
exit();
}
diff --git a/tests/adhoc/dmz01.sh b/tests/adhoc/dmz01.sh
new file mode 100644
index 0000000..8854ca2
--- /dev/null
+++ b/tests/adhoc/dmz01.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+# Copyright 2015 Open Platform for NFV Project, Inc. and its contributors
+#
+# 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.
+#
+
+# What this is: An OpenStack Congress policy test. Sets up and validates policy
+# creation and execution for:
+# 1) Identifying VMs connected to a DMZ (currently identified through a
+# specifically-named security group)
+# 2) Identifying VMs connected per (1), which are by policy not allowed to be
+# (currently implemented through an image tag intended to identify images
+# that are "authorized" i.e. tested and secure, to be DMZ-connected)
+#
+# Status: this is a work in progress, under test. Test (1) has been verified,
+# Test (2) is still in development.
+#
+# How to use:
+# Install test server per https://wiki.opnfv.org/copper/academy/congress/test
+# $ source ~/git/copper/tests/adhoc/dmz01.sh
+# After test, cleanup with (not yet implemented)
+# $ source ~/git/copper/tests/adhoc/dmz01-clean.sh
+
+set -x #echo on
+
+source ~/admin-openrc.sh
+
+glance --os-image-api-version 1 image-create --name cirros-0.3.3-x86_64-dmz --disk-format qcow2 --location http://download.cirros-cloud.net/0.3.3/cirros-0.3.3-x86_64-disk.img --container-format bare
+
+IMAGE_ID=$(glance image-list | awk "/ cirros-0.3.3-x86_64-dmz / { print \$2 }")
+
+glance --os-image-api-version 2 image-tag-update $IMAGE_ID "dmz"
+
+neutron net-create public --router:external=true --provider:network_type=flat --provider:physical_network=physnet1
+
+neutron subnet-create --disable-dhcp public 192.168.10.0/24
+
+neutron net-create internal
+
+neutron subnet-create internal 10.0.0.0/24 --name internal --gateway 10.0.0.1 --enable-dhcp --allocation-pool start=10.0.0.2,end=10.0.0.254 --dns-nameserver 8.8.8.8
+
+neutron router-create external
+
+neutron router-gateway-set external public
+
+neutron router-interface-add external subnet=internal
+
+INTERNAL_NET=$(neutron net-list | awk "/ internal / { print \$2 }")
+
+neutron security-group-create dmz
+
+neutron security-group-rule-create --direction ingress dmz
+
+nova boot --flavor m1.tiny --image cirros-0.3.3-x86_64 --nic net-id=$INTERNAL_NET --security-groups dmz cirros1
+
+nova boot --flavor m1.tiny --image cirros-0.3.3-x86_64-dmz --nic net-id=$INTERNAL_NET --security-groups dmz cirros2
+
+openstack congress policy create test
+
+openstack congress policy rule create test "dmz_server(x) :- nova:servers(id=x,status='ACTIVE'), neutronv2:ports(id, device_id, status='ACTIVE'), neutronv2:security_group_port_bindings(pid, sg), neutronv2:security_groups(sg,name='dmz')" --name dmz_server
+
+# currently failing "Rule already exists::An unknown exception occurred. (HTTP 409)..."
+openstack congress policy rule create test "dmz_placement_error(id) :- nova:servers(
+id,name,hostId,status,tenant_id,user_id,image,flavor,OS1,OS2), not glance:images(image,tags='dmz'), dmz_server(x)" --name dmz_placement_error
+
+# validated rules created during test development
+# openstack congress policy rule create test "active_servers(x) :- nova:servers(id=x, status='ACTIVE')" --name active_servers
+# openstack congress policy rule create test "dmz_port(id) :- neutronv2:security_group_port_bindings(id,sg), neutronv2:security_groups(sg,name='dmz')" --name dmz_port
+#
+# rules under test
+# openstack congress policy rule create test "cirros(x) :- glance:images(id=x,name='cirros-0.3.3-x86_64')" --name cirros
+# openstack congress policy rule create test "image_notags(x) :- glance:images(id=x,tags='')" --name image_notags
+# to remove a policy rule
+# openstack congress policy rule delete test nondmz_image
+
+set +x #echo off
diff --git a/tests/adhoc/smoke01-clean.sh b/tests/adhoc/smoke01-clean.sh
new file mode 100644
index 0000000..c50d667
--- /dev/null
+++ b/tests/adhoc/smoke01-clean.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+set -x #echo on
+
+instance=$(nova list | awk "/ cirros1 / { print \$2 }")
+if [ "$instance" != "" ]; then nova delete $instance
+fi
+
+instance=$(nova list | awk "/ cirros2 / { print \$2 }")
+if [ "$instance" != "" ]; then nova delete $instance
+fi
+
+router=$(neutron router-list | awk "/ external / { print \$2 }")
+
+internal_interface=$(neutron router-port-list $router | grep 10.0.0.1 | awk '{print $2}')
+
+if [ "$internal_interface" != "" ]; then neutron router-interface-delete $router port=$internal_interface
+fi
+
+public_interface=$(neutron router-port-list $router | grep 191.168.10.2 | awk '{print $2}')
+
+if [ "$public_interface" != "" ]; then neutron router-interface-delete $router port=$public_interface
+fi
+
+neutron router-interface-delete $router $internal_interface
+
+neutron router-gateway-clear external
+
+neutron router-delete external
+
+port=$(neutron port-list | awk "/ 10.0.0.1 / { print \$2 }")
+
+if [ "$port" != "" ]; then neutron port-delete $port
+fi
+
+port=$(neutron port-list | awk "/ 10.0.0.2 / { print \$2 }")
+
+if [ "$port" != "" ]; then neutron port-delete $port
+fi
+
+neutron subnet-delete internal
+
+neutron net-delete internal
+
+neutron subnet-delete public
+
+neutron net-delete public
+
+set +x #echo off
diff --git a/tests/adhoc/smoke01.sh b/tests/adhoc/smoke01.sh
new file mode 100644
index 0000000..911fabb
--- /dev/null
+++ b/tests/adhoc/smoke01.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+# Copyright 2015 Open Platform for NFV Project, Inc. and its contributors
+#
+# 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.
+#
+
+# What this is: A basic test to validate an OPNFV install. Creates an image,
+# public and private networks, a router, and launches two VMs connected to the
+# private network and thru the router, to the internet.
+#
+# Status: this is a work in progress, under test. Automated ping test to the
+# internet and between VMs has not yet been implemented.
+#
+# How to use:
+# Install test server per https://wiki.opnfv.org/copper/academy/congress/test
+# $ source ~/git/copper/tests/adhoc/smoke01.sh
+# After test, cleanup with
+# $ source ~/git/copper/tests/adhoc/smoke01-clean.sh
+
+set -x #echo on
+
+source ~/admin-openrc.sh
+
+openstack service list
+
+glance --os-image-api-version 1 image-create --name cirros-0.3.3-x86_64 --disk-format qcow2 --location http://download.cirros-cloud.net/0.3.3/cirros-0.3.3-x86_64-disk.img --container-format bare
+
+neutron net-create public --router:external=true --provider:network_type=flat --provider:physical_network=physnet1
+
+neutron subnet-create --disable-dhcp public 192.168.10.0/24
+
+neutron net-create internal
+
+neutron subnet-create internal 10.0.0.0/24 --name internal --gateway 10.0.0.1 --enable-dhcp --allocation-pool start=10.0.0.2,end=10.0.0.254 --dns-nameserver 8.8.8.8
+
+neutron router-create external
+
+neutron router-gateway-set external public
+
+neutron router-interface-add external subnet=internal
+
+INTERNAL_NET=$(neutron net-list | awk "/ internal / { print \$2 }")
+
+nova boot --flavor m1.tiny --image cirros-0.3.3-x86_64 --nic net-id=$INTERNAL_NET cirros1
+
+nova boot --flavor m1.tiny --image cirros-0.3.3-x86_64 --nic net-id=$INTERNAL_NET cirros2
+
+set +x #echo off
diff --git a/tests/setup/trusty-copper.sh b/tests/setup/trusty-copper.sh
new file mode 100644
index 0000000..d9ecf1c
--- /dev/null
+++ b/tests/setup/trusty-copper.sh
@@ -0,0 +1,152 @@
+#!/bin/bash
+# Copyright 2015 Open Platform for NFV Project, Inc. and its contributors
+#
+# 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.
+#
+# What this is: A shell script for installing a test driver for
+# OpenStack Congress on Ubuntu.
+# Status: this is a work in progress, under test. Some steps are
+# manual.
+#
+# How to use:
+# Install OPNFV per https://wiki.opnfv.org/copper/academy/joid
+# $ source ~/git/copper/tests/setup/trusty-copper.sh
+#
+
+Following are notes on creating a container as test driver for Congress. This is based upon an Ubuntu host as installed by JOID.
+
+# === Create and Activate the Container ===
+
+# <code>
+# On the jumphost
+sudo lxc-create -n trusty-copper -t /usr/share/lxc/templates/lxc-ubuntu -- -b ubuntu ~/opnfv
+
+sudo lxc-start -n trusty-copper -d
+
+sudo lxc-info --name trusty-copper
+
+HOST_IP=$(sudo lxc-info --name trusty-copper | grep IP | awk "/ / { print \$2 }")
+echo HOST_IP = $HOST_IP
+
+# === Login and configure the test server ===
+# <code>
+ssh ubuntu@$HOST_IP
+sudo apt-get update
+sudo apt-get upgrade -y
+
+# Install pip
+sudo apt-get install python-pip -y
+
+# Install java
+sudo apt-get install default-jre -y
+
+# Install other dependencies
+sudo apt-get install git gcc python-dev libxml2 libxslt1-dev libzip-dev php5-curl -y
+
+# Setup OpenStack environment variables per your OPNFV install
+export CONGRESS_HOST=192.168.10.117
+export KEYSTONE_HOST=192.168.10.108
+export CEILOMETER_HOST=192.168.10.105
+export CINDER_HOST=192.168.10.101
+export GLANCE_HOST=192.168.10.106
+export HEAT_HOST=192.168.10.107
+export NEUTRON_HOST=192.168.10.111
+export NOVA_HOST=192.168.10.112
+source ~/admin-openrc.sh
+
+# Install and test OpenStack client
+mkdir ~/git
+cd git
+git clone https://github.com/openstack/python-openstackclient.git
+cd python-openstackclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+openstack service list
+
+# Install and test Congress client
+cd ~/git
+git clone https://github.com/openstack/python-congressclient.git
+cd python-congressclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+openstack congress driver list
+
+# Install and test Glance client
+cd ~/git
+git clone https://github.com/openstack/python-glanceclient.git
+cd python-glanceclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+glance image-list
+
+# Install and test Neutron client
+cd ~/git
+git clone https://github.com/openstack/python-neutronclient.git
+cd python-neutronclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+neutron net-list
+
+# Install and test Nova client
+cd ~/git
+git clone https://github.com/openstack/python-novaclient.git
+cd python-novaclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+nova hypervisor-list
+
+# Install and test Keystone client
+cd ~/git
+git clone https://github.com/openstack/python-keystoneclient.git
+cd python-keystoneclient
+git checkout stable/liberty
+sudo pip install -r requirements.txt
+sudo python setup.py install
+
+# </code>
+
+# === Setup the Congress Test Webapp ===
+
+# <code>
+# Clone Copper (if not already cloned in user home)
+cd ~/git
+if [ ! -d ~/git/copper ]; then git clone https://gerrit.opnfv.org/gerrit/copper; fi
+
+# Copy the Apache config
+sudo cp ~/git/copper/components/congress/test-webapp/www/ubuntu-apache2.conf /etc/apache2/apache2.conf
+
+# Point proxy.php to the Congress server per your install
+sed -i -- "s/192.168.10.117/$CONGRESS_HOST/g" \
+ ~/git/copper/components/congress/test-webapp/www/html/proxy/index.php
+
+# Copy the webapp to the Apache root directory and fix permissions
+sudo cp -R ~/git/copper/components/congress/test-webapp/www/html /var/www
+sudo chmod 755 /var/www/html -R
+
+# Make webapp log directory and set permissions
+mkdir ~/logs
+chmod 777 ~/logs
+
+# Restart Apache
+sudo service apache2 restart
+# </code>
+
+# === Using the Test Webapp ===
+# Browse to the trusty-copper server IP address.
+
+# Interactive options are meant to be self-explanatory given a basic familiarity with the Congress service and data model. But the app will be developed with additional features and UI elements.