Initial import from external repository
External repo: https://github.com/mganguli/RSC Commit: 49199a82045f1d6f231eb477de3dbcd59492e9d9 Change-Id: I9eaec387605a39ba5e4c571026cacb1845938231
This commit is contained in:
parent
5eceb8d3e8
commit
0ac90c5522
70
.gitignore
vendored
Normal file
70
.gitignore
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg*
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
cover
|
||||
.tox
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
.venv
|
||||
|
||||
# Functional test
|
||||
functional-tests.log
|
||||
functional_creds.conf
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
.idea
|
||||
|
||||
# Complexity
|
||||
output/*.html
|
||||
output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Editors
|
||||
*~
|
||||
.*.swp
|
||||
.*sw?
|
||||
*.DS_Store
|
||||
|
||||
# generated config file
|
||||
etc/magnum/magnum.conf.sample
|
||||
|
||||
# Files created by releasenotes build
|
||||
releasenotes/build
|
||||
|
||||
# UI Node files
|
||||
valence/ui/node_modules
|
||||
valence/ui/npm-debug.log
|
17
CONTRIBUTING.rst
Normal file
17
CONTRIBUTING.rst
Normal file
@ -0,0 +1,17 @@
|
||||
If you would like to contribute to the development of OpenStack, you must
|
||||
follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
If you already have a good understanding of how the system works and your
|
||||
OpenStack accounts are set up, you can skip to the development workflow
|
||||
section of this documentation to learn how changes to OpenStack should be
|
||||
submitted for review via the Gerrit tool:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/plasma
|
4
HACKING.rst
Normal file
4
HACKING.rst
Normal file
@ -0,0 +1,4 @@
|
||||
plasma Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
7
MANIFEST.in
Normal file
7
MANIFEST.in
Normal file
@ -0,0 +1,7 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
||||
#recursive-include public *
|
91
README.rst
Normal file
91
README.rst
Normal file
@ -0,0 +1,91 @@
|
||||
=========================
|
||||
Openstack Valence Project
|
||||
=========================
|
||||
|
||||
Valence is a service for lifecycle management of pooled bare-metal hardware infrastructure such as Intel(R) Rack Scale architecture which uses Redfish(TM) as one of the management protocols.
|
||||
|
||||
:Free software: Apache license
|
||||
:Wiki: https://wiki.openstack.org/wiki/Valence
|
||||
:Source: http://git.openstack.org/cgit/openstack/rsc
|
||||
:Bugs: http://bugs.launchpad.net/openstack-valence
|
||||
|
||||
|
||||
===========================
|
||||
Download and Installation
|
||||
===========================
|
||||
|
||||
The following steps capture how to install valence. All installation steps require super user permissions.
|
||||
|
||||
********************
|
||||
Valence installation
|
||||
********************
|
||||
|
||||
1. Install software dependencies
|
||||
|
||||
``$ sudo apt-get install git python-pip rabbitmq-server libyaml-0-2 python-dev``
|
||||
|
||||
2. Configure RabbitMq Server
|
||||
|
||||
``$ sudo rabbitmqctl add_user rsd rsd #user this username/pwd in valence.conf``
|
||||
|
||||
``$ sudo rabbitmqctl set_user_tags rsd administrator``
|
||||
|
||||
``$ sudo rabbitmqctl set_permissions rsd ".*" ".*" ".*"``
|
||||
|
||||
3. Clone the Valence code from git repo and change the directory to root Valence folder.
|
||||
|
||||
4. Install all necessary software pre-requisites using the pip requirements file.
|
||||
|
||||
``$ sudo -E pip install -r requirements.txt``
|
||||
|
||||
5. Execute the 'install_valence.sh' file the Valence root directory.
|
||||
|
||||
``$ ./install_valence.sh``
|
||||
|
||||
6. Check the values in valence.conf located at /etc/valence/valence.conf
|
||||
|
||||
``set the ip/credentials of podm for which this Valence will interact``
|
||||
|
||||
``set the rabbitmq user/password to the one given above(Step 2)``
|
||||
|
||||
7. Check the values in /etc/init/valence-api.conf, /etc/init/valence-controller.conf
|
||||
|
||||
8. Start api and controller services
|
||||
|
||||
``$ service valence-api start``
|
||||
|
||||
``$ service valence-controller start``
|
||||
|
||||
9. Logs are located at /var/logs/valence/
|
||||
|
||||
****************
|
||||
GUI installation
|
||||
****************
|
||||
Please refer to the installation steps in the ui/README file.
|
||||
|
||||
|
||||
**********
|
||||
Components
|
||||
**********
|
||||
|
||||
Valence follows the typical OpenStack project setup. The components are listed below:
|
||||
|
||||
valence-api
|
||||
-----------
|
||||
A pecan based daemon to expose Valence REST APIs. The api service communicates to the controller through AMQP.
|
||||
|
||||
valence-controller
|
||||
--------------
|
||||
The controller implements all the handlers for Plasma-api. It reads requests from the AMQP queue, process it and send the reponse back to the caller.
|
||||
|
||||
valence-ui
|
||||
--------
|
||||
valence-ui provides a GUI interface to invoke Valence APIs.
|
||||
|
||||
==========
|
||||
Features
|
||||
==========
|
||||
Please refer the Valence blueprints for supported and in-the-pipeline features.
|
||||
``https://blueprints.launchpad.net/plasma``
|
||||
|
||||
|
3
doc/README.md
Normal file
3
doc/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
RSC API spec and RSC mockup file.
|
||||
|
||||
<chester.kuo@intel.com>
|
30
doc/api-mockup/index.json
Normal file
30
doc/api-mockup/index.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name" : "OpenStack Plasma API",
|
||||
"description" : "Plasma is an OpenStack project which aims to provide node composition based on redfish API.",
|
||||
"default_version" : {
|
||||
"status" : "CURRENT",
|
||||
"version" : "1.1",
|
||||
"links" : [
|
||||
{
|
||||
"rel" : "self",
|
||||
"href" : "http://openstack.example.com:8881/v1/"
|
||||
}
|
||||
],
|
||||
"id" : "v1",
|
||||
"min_version" : "1.0"
|
||||
},
|
||||
"versions" : [
|
||||
{
|
||||
"status" : "CURRENT",
|
||||
"links" : [
|
||||
{
|
||||
"href" : "http://openstack.example.com:8881/v1/",
|
||||
"rel" : "self"
|
||||
}
|
||||
],
|
||||
"id" : "v1",
|
||||
"version" : "1.1",
|
||||
"min_version" : "1.0"
|
||||
}
|
||||
]
|
||||
}
|
33
doc/api-mockup/v1/flavors/criteria/index.json
Normal file
33
doc/api-mockup/v1/flavors/criteria/index.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"criteria" : [
|
||||
{
|
||||
"id": "8f70656e-7374-6163-6b20-342065766222",
|
||||
"links" : [
|
||||
{
|
||||
"href": "http://openstack.example.com/v1/criteria/8f70656e-7374-6163-6b20-342065766222",
|
||||
"rel" : "self"
|
||||
},
|
||||
{
|
||||
"href" : "http://openstack.example.com/criteria/8f70656e-7374-6163-6b20-342065766222",
|
||||
"rel" : "bookmakr"
|
||||
}
|
||||
],
|
||||
"name" : "criteria 1"
|
||||
},
|
||||
{
|
||||
|
||||
"id": "8170656e-7374-6163-6b20-342065766112",
|
||||
"links" : [
|
||||
{
|
||||
"href": "http://openstack.example.com/v1/criteria/8170656e-7374-6163-6b20-342065766112",
|
||||
"rel" : "self"
|
||||
},
|
||||
{
|
||||
"href" : "http://openstack.example.com/criteria/8170656e-7374-6163-6b20-342065766112",
|
||||
"rel" : "bookmakr"
|
||||
}
|
||||
],
|
||||
"name" : "criteria 2"
|
||||
}
|
||||
]
|
||||
}
|
39
doc/api-mockup/v1/flavors/index.json
Normal file
39
doc/api-mockup/v1/flavors/index.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"flavors": [
|
||||
{
|
||||
"id": "67730a1e-42b3-4813-8940-b961dcd0293c",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v1/flavors/67730a1e-42b3-4813-8940-b961dcd0293c",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/flavors/67730a1e-42b3-4813-8940-b961dcd0293c",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "flavor1",
|
||||
"criteria" : [
|
||||
{"id" : "8f70656e-7374-6163-6b20-342065766222"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "30abc156-d673-4e7c-bf2a-0a5098e14878",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v1/flavors/30abc156-d673-4e7c-bf2a-0a5098e14878",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/flavors/30abc156-d673-4e7c-bf2a-0a5098e14878",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "flavor2",
|
||||
"criteria" : [
|
||||
{"id" : "8f70656e-7374-6163-6b20-342065766222"},
|
||||
{"id" : "8170656e-7374-6163-6b20-342065766211"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
17
doc/api-mockup/v1/flavors/post.json
Normal file
17
doc/api-mockup/v1/flavors/post.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"flavors" : {
|
||||
"criteria_id" : "8f70656e737461636b20342065766222",
|
||||
"id" : "10",
|
||||
"name" : "flavor 10",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v1/flavors/10",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/flavors/10",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
50
doc/api-mockup/v1/index.json
Normal file
50
doc/api-mockup/v1/index.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"id" : "v1",
|
||||
"links" : [
|
||||
{
|
||||
"href" : "http://openstack.example.com:8881/v1/",
|
||||
"rel" : "self"
|
||||
},
|
||||
{
|
||||
"rel" : "describedby",
|
||||
"type" : "text/html",
|
||||
"href" : "http://docs.openstack.org/developer/plasma/dev/api-spec-v1.html"
|
||||
}
|
||||
],
|
||||
"nodes" : [
|
||||
{
|
||||
"rel" : "self",
|
||||
"href" : "http://openstack.example.com:8881/v1/nodes/"
|
||||
},
|
||||
{
|
||||
"rel" : "bookmark",
|
||||
"href" : "http://openstack.example.com:8881/nodes/"
|
||||
}
|
||||
],
|
||||
"storages" : [
|
||||
{
|
||||
"href" : "http://openstack.example.com:8881/v1/storages/",
|
||||
"rel" : "self"
|
||||
},
|
||||
{
|
||||
"rel" : "bookmark",
|
||||
"href" : "http://openstack.example.com:8881/storages/"
|
||||
}
|
||||
],
|
||||
"flavors" : [
|
||||
{
|
||||
"href" : "http://openstack.example.com:8881/v1/flavors/",
|
||||
"rel" : "self"
|
||||
},
|
||||
{
|
||||
"rel" : "bookmark",
|
||||
"href" : "http://openstack.example.com:8881/flavors/"
|
||||
}
|
||||
],
|
||||
"media_types" : [
|
||||
{
|
||||
"type" : "application/vnd.openstack.plasma.v1+json",
|
||||
"base" : "application/json"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
{
|
||||
"node" : {
|
||||
"id" : "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
"ComposedNodeState" : "Off",
|
||||
"boot_source" : "Localdisk",
|
||||
"pending_boot_source" : "PXE",
|
||||
"node_state" : "Allocated",
|
||||
"health_status" : "OK",
|
||||
"name" : null,
|
||||
"pooling_group_id" : "11z23344-0099-7766-5544-33225511",
|
||||
"metadata" : {
|
||||
"nic" : [
|
||||
{"mac" : "f1:12:44:55:66:77"},
|
||||
{"mac" : "f2:44:44:44:44:88"}
|
||||
],
|
||||
"mgmt_mac" : "00:AA:BB:CC:DD:EE",
|
||||
"podid" : "POD1",
|
||||
"rackid" : "Rack2",
|
||||
"slotid" : "3",
|
||||
"board_serialno" : "2M220100SL"
|
||||
},
|
||||
"node_properties" : {
|
||||
"cpu_arch" : "x86_64",
|
||||
"cpu_count" : "2",
|
||||
"memory_size_gb" : "32",
|
||||
"network" : [
|
||||
{
|
||||
"type" : "ethernet",
|
||||
"speed" : "40000000"
|
||||
}
|
||||
],
|
||||
"memory_type" : "DDR4",
|
||||
"storage" : [
|
||||
{
|
||||
"type" : "SSD",
|
||||
"volume_gb" : "40"
|
||||
}
|
||||
]
|
||||
},
|
||||
"created_at" : "2016-04-20T15:40:00+00:00",
|
||||
"updated_at" : "2016-04-20T15:40:00+00:00",
|
||||
"links": [
|
||||
{
|
||||
"rel" : "self",
|
||||
"href" : "https://openstack.example.com/v1/nodes/4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
},
|
||||
{
|
||||
"rel" : "boomark",
|
||||
"href" : "https://openstack.example.com/nodes/4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"storagevolumeAttachments": [
|
||||
{
|
||||
"device": "/dev/sdd",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"serverId": "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
},
|
||||
{
|
||||
"device": "/dev/sdc",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f804",
|
||||
"serverId": "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
||||
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f804"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
{
|
||||
"node" : {
|
||||
"id" : "ee1ecc3c-d3dd-f4ff-a6aa-uu7uk9k0",
|
||||
"nodestate" : "Off",
|
||||
"boot_source" : "Localdisk",
|
||||
"pending_boot_source" : "PXE",
|
||||
"pooling_group_id" : "11z23344-0099-7766-5544-33225511",
|
||||
"health_status" : "OK",
|
||||
"name" : null,
|
||||
"metadata" : {
|
||||
"nic" : [
|
||||
{"mac" : "f1:12:44:55:66:77"},
|
||||
{"mac" : "f2:44:44:44:44:88"}
|
||||
],
|
||||
"mgmt_mac" : "00:AA:BB:CC:DD:EE",
|
||||
"podid" : "POD1",
|
||||
"rackid" : "Rack2",
|
||||
"slotid" : "3",
|
||||
"board_serialno" : "2M220100SL"
|
||||
},
|
||||
"node_properties" : {
|
||||
"cpu_arch" : "x86_64",
|
||||
"cpu_count" : "2",
|
||||
"memory_size_gb" : "32",
|
||||
"network" : [
|
||||
{
|
||||
"type" : "ethernet",
|
||||
"speed" : "40000000"
|
||||
}
|
||||
],
|
||||
"memory_type" : "DDR4",
|
||||
"storage" : [
|
||||
{
|
||||
"type" : "SSD",
|
||||
"volume_gb" : "40"
|
||||
}
|
||||
]
|
||||
},
|
||||
"created_at" : "2016-04-20T15:40:00+00:00",
|
||||
"updated_at" : "2016-04-20T15:40:00+00:00",
|
||||
"links": [
|
||||
{
|
||||
"rel" : "self",
|
||||
"href" : "https://openstack.example.com/v1/nodes/ee1ecc3c-d3dd-f4ff-a6aa-uu7uk9k0"
|
||||
},
|
||||
{
|
||||
"rel" : "boomark",
|
||||
"href" : "https://openstack.example.com/nodes/ee1ecc3c-d3dd-f4ff-a6aa-uu7uk9k0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
34
doc/api-mockup/v1/nodes/index.json
Normal file
34
doc/api-mockup/v1/nodes/index.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"nodes" : [
|
||||
{
|
||||
"id" : "ee1ecc3c-d3dd-f4ff-a6aa-uu7uk9k0",
|
||||
"name" : "Server 1" ,
|
||||
"nodestate" : "PoweredOn" ,
|
||||
"links": [
|
||||
{
|
||||
"rel" : "self",
|
||||
"href" : "https://openstack.example.com/v1/nodes/ee1ecc3c-d3dd-f4ff-a6aa-uu7uk9k0"
|
||||
},
|
||||
{
|
||||
"href" : "https://openstack.example.com/nodes/ee1ecc3c-d3dd-f4ff-a6aa-uu7uk9k0",
|
||||
"rel" : "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "4d8c3732-a248-40ed-bebc-539a6ffd25c0" ,
|
||||
"name" : "Server 2",
|
||||
"nodestate" : "PoweredOff" ,
|
||||
"links" : [
|
||||
{
|
||||
"ref" : "self",
|
||||
"href" : "https://openstack.example.com/v1/nodes/4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
},
|
||||
{
|
||||
"ref" : "bookmark",
|
||||
"href" : "https://openstack.example.com/nodes/4d8c3732-a248-40ed-bebc-539a6ffd25c0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"storage_device" :
|
||||
{
|
||||
"deviceId" : "4c16a45b-b029-49c4-af84-1abcf458a062",
|
||||
"pooling_group_id" : "11z23344-0099-7766-5544-33225511",
|
||||
"health_status" : "critical",
|
||||
"capacity_mb" : "1000",
|
||||
"property_foo1" : "value_bar1",
|
||||
"property_foo2" : "value_bar2"
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"storage_device" :
|
||||
{
|
||||
"deviceId" : "bbfddf09-4d7e-40d5-88a9-8acfb2f88c21",
|
||||
"pooling_group_id" : "11z23344-0099-7766-5544-33225511",
|
||||
"health_status" : "critical",
|
||||
"capacity_mb" : "1000",
|
||||
"property_foo1" : "value_bar1",
|
||||
"property_foo2" : "value_bar2"
|
||||
}
|
||||
}
|
34
doc/api-mockup/v1/storages/index.json
Normal file
34
doc/api-mockup/v1/storages/index.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"storges" : [
|
||||
{
|
||||
"deviceId" : "bbfddf09-4d7e-40d5-88a9-8acfb2f88c21",
|
||||
"pooling_group_id" : "11z23344-0099-7766-5544-33225511",
|
||||
"allocate_status" : "allocated",
|
||||
"links" : [
|
||||
{
|
||||
"ref" : "self",
|
||||
"href" : "https://openstack.example.com/v1/storages/bbfddf09-4d7e-40d5-88a9-8acfb2f88c21"
|
||||
},
|
||||
{
|
||||
"ref" : "bookmark",
|
||||
"href" : "https://openstack.example.com/storages/bbfddf09-4d7e-40d5-88a9-8acfb2f88c21"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"deviceId" : "4c16a45b-b029-49c4-af84-1abcf458a062",
|
||||
"pooling_group_id" : "22zz3344-0099-7766-5544-33225512",
|
||||
"allocate_status" : "available",
|
||||
"links" : [
|
||||
{
|
||||
"ref" : "self",
|
||||
"href" : "https://openstack.example.com/v1/storages/4c16a45b-b029-49c4-af84-1abcf458a062"
|
||||
},
|
||||
{
|
||||
"ref" : "bookmark",
|
||||
"href" : "https://openstack.example.com/storages/4c16a45b-b029-49c4-af84-1abcf458a062"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
BIN
doc/api/VALENCE_API-v0.4.1.docx
Normal file
BIN
doc/api/VALENCE_API-v0.4.1.docx
Normal file
Binary file not shown.
75
doc/source/conf.py
Normal file
75
doc/source/conf.py
Normal file
@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'plasma'
|
||||
copyright = u'2016, OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
4
doc/source/contributing.rst
Normal file
4
doc/source/contributing.rst
Normal file
@ -0,0 +1,4 @@
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
.. include:: ../../CONTRIBUTING.rst
|
24
doc/source/index.rst
Normal file
24
doc/source/index.rst
Normal file
@ -0,0 +1,24 @@
|
||||
.. plasma documentation master file, created by
|
||||
sphinx-quickstart on Tue Jul 9 22:26:36 2013.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to plasma's documentation!
|
||||
========================================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
15
doc/source/init/valence-api.conf
Normal file
15
doc/source/init/valence-api.conf
Normal file
@ -0,0 +1,15 @@
|
||||
description "Valence API server"
|
||||
|
||||
start on runlevel [2345]
|
||||
stop on runlevel [!2345]
|
||||
|
||||
env PYTHON_HOME=/home/${CHUID}/.local/bin
|
||||
|
||||
# change the chuid to match yours
|
||||
exec start-stop-daemon --start --verbose --chuid ${CHUID} \
|
||||
--name valence-api \
|
||||
--exec $PYTHON_HOME/valence-api -- \
|
||||
--log-file=/var/log/valence/valence-api.log
|
||||
|
||||
respawn
|
||||
|
14
doc/source/init/valence-controller.conf
Executable file
14
doc/source/init/valence-controller.conf
Executable file
@ -0,0 +1,14 @@
|
||||
description "Valence Controller server"
|
||||
|
||||
start on runlevel [2345]
|
||||
stop on runlevel [!2345]
|
||||
|
||||
env PYTHON_HOME=/home/${CHUID}/.local/bin
|
||||
|
||||
exec start-stop-daemon --start --verbose --chuid ${CHUID} \
|
||||
--name valence-controller \
|
||||
--exec $PYTHON_HOME/valence-controller -- \
|
||||
--log-file=/var/log/valence/valence-controller.log
|
||||
|
||||
respawn
|
||||
|
12
doc/source/installation.rst
Normal file
12
doc/source/installation.rst
Normal file
@ -0,0 +1,12 @@
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install plasma
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv plasma
|
||||
$ pip install plasma
|
1
doc/source/readme.rst
Normal file
1
doc/source/readme.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../README.rst
|
7
doc/source/usage.rst
Normal file
7
doc/source/usage.rst
Normal file
@ -0,0 +1,7 @@
|
||||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
To use plasma in a project::
|
||||
|
||||
import plasma
|
71
doc/ui-proxy/apache/README.md
Normal file
71
doc/ui-proxy/apache/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
Apache proxy service to pod-manager API
|
||||
=======================================
|
||||
|
||||
This manual has been verified on Ubuntu 16.04 + Apache (2.4.18-2ubuntu3.1).
|
||||
|
||||
##Install
|
||||
1. Use package manager tool on your distribution to install apache server.
|
||||
```
|
||||
sudo apt-get install apache2
|
||||
```
|
||||
2. Enable all related modules for Apache server.
|
||||
```
|
||||
sudo a2enmod proxy_http proxy ssl headers
|
||||
```
|
||||
3. Setup virtual host for proxy to podm.
|
||||
```
|
||||
sudo cp podm-proxy.conf /etc/apache2/sites-available
|
||||
sudo a2ensite podm-proxy
|
||||
```
|
||||
4. Add listening port 6000.
|
||||
Add "Listen 6000" into Apaches port setting file /etc/apache2/ports.conf.
|
||||
* If need, you can change it to any available port in your server. In this case, please remember to update
|
||||
"<VirtualHost *:6000>" in /etc/apache2/sites-available/podm-proxy.conf.
|
||||
5. Update podm address in /etc/apache2/sites-available/podm-proxy.conf.
|
||||
By default, the podm api is pointed to https://127.0.0.1:8443/. Update it to fit your environment.
|
||||
6. Restart Apache server.
|
||||
```
|
||||
sudo systemctl restart apache2
|
||||
```
|
||||
|
||||
The proxy is available under http://127.0.0.1:6000/redfish/v1.
|
||||
```
|
||||
curl http://127.0.0.1:6000/redfish/v1/
|
||||
{
|
||||
"@odata.context" : "/redfish/v1/$metadata#ServiceRoot",
|
||||
"@odata.id" : "/redfish/v1",
|
||||
"@odata.type" : "#ServiceRoot.1.0.0.ServiceRoot",
|
||||
"Id" : "ServiceRoot",
|
||||
"Name" : "Service root",
|
||||
"RedfishVersion" : "1.0.0",
|
||||
"UUID" : "3c414ee3-bd28-4e6c-b9e8-fd8008dbd0ce",
|
||||
"Chassis" : {
|
||||
"@odata.id" : "/redfish/v1/Chassis"
|
||||
},
|
||||
"Services" : {
|
||||
"@odata.id" : "/redfish/v1/Services"
|
||||
},
|
||||
"Systems" : {
|
||||
"@odata.id" : "/redfish/v1/Systems"
|
||||
},
|
||||
"Managers" : {
|
||||
"@odata.id" : "/redfish/v1/Managers"
|
||||
},
|
||||
"EventService" : {
|
||||
"@odata.id" : "/redfish/v1/EventService"
|
||||
},
|
||||
"Nodes" : {
|
||||
"@odata.id" : "/redfish/v1/Nodes"
|
||||
},
|
||||
"EthernetSwitches" : {
|
||||
"@odata.id" : "/redfish/v1/EthernetSwitches"
|
||||
},
|
||||
"Oem" : {
|
||||
"Intel_RackScale" : {
|
||||
"@odata.type" : "#Intel.Oem.ServiceRoot",
|
||||
"ApiVersion" : "1.2.0"
|
||||
}
|
||||
},
|
||||
"Links" : { }
|
||||
}
|
||||
```
|
49
doc/ui-proxy/apache/podm-proxy.conf
Normal file
49
doc/ui-proxy/apache/podm-proxy.conf
Normal file
@ -0,0 +1,49 @@
|
||||
<VirtualHost *:6000>
|
||||
# Reserve proxy to podm
|
||||
ProxyRequests Off
|
||||
|
||||
# If needed, change following default pod address https://127.0.0.1:8443/
|
||||
# to real podm api in your environment.
|
||||
ProxyPass / https://127.0.0.1:8443/
|
||||
ProxyPassReverse / https://127.0.0.1:8443/
|
||||
|
||||
<Proxy *>
|
||||
Order Deny,Allow
|
||||
Allow from all
|
||||
</Proxy>
|
||||
|
||||
# Ignore ssl certificate check when proxy request to podm
|
||||
SSLProxyEngine on
|
||||
SSLProxyVerify none
|
||||
SSLProxyCheckPeerCN off
|
||||
SSLProxyCheckPeerName off
|
||||
SSLProxyCheckPeerExpire off
|
||||
|
||||
# Append http header in request to podm to set up authorization.
|
||||
# Default username/password: admin/admin. Please change to fit your specific setting.
|
||||
RequestHeader set Authorization 'Basic YWRtaW46YWRtaW4='
|
||||
RequestHeader set User-Agent 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
|
||||
|
||||
# Append http header in response to enable CORS
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
Header set Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS"
|
||||
Header set Access-Control-Allow-Headers "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token"
|
||||
|
||||
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
|
||||
# error, crit, alert, emerg.
|
||||
# It is also possible to configure the loglevel for particular
|
||||
# modules, e.g.
|
||||
#LogLevel info ssl:warn
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
# include a line for only one particular virtual host. For example the
|
||||
# following line enables the CGI configuration for this host only
|
||||
# after it has been globally disabled with "a2disconf".
|
||||
#Include conf-available/serve-cgi-bin.conf
|
||||
</VirtualHost>
|
||||
|
||||
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
|
40
etc/valence/valence.conf.sample
Normal file
40
etc/valence/valence.conf.sample
Normal file
@ -0,0 +1,40 @@
|
||||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
auth_strategy=noauth
|
||||
|
||||
# Log to this file. Make sure the user running rsc has
|
||||
# permissions to write to this file!
|
||||
log_file = rsc.log
|
||||
|
||||
|
||||
log_dir=/var/log/rsc
|
||||
rpc_response_timeout = 300
|
||||
|
||||
|
||||
[api]
|
||||
#address to bind the server to
|
||||
bind_host = 0.0.0.0
|
||||
|
||||
# Port the bind the server to
|
||||
bind_port = 8181
|
||||
|
||||
[oslo_messaging_rabbit]
|
||||
rabbit_host = localhost
|
||||
rabbit_port = 5672
|
||||
rabbit_userid = rsc
|
||||
rabbit_password = rsc
|
||||
|
||||
[podm]
|
||||
#url=http://10.223.197.204
|
||||
url=http://<ip address>
|
||||
user=<user>
|
||||
password=<password>
|
||||
|
||||
[conductor]
|
||||
#topic=rsc-conductor
|
||||
|
42
install_valence.sh
Executable file
42
install_valence.sh
Executable file
@ -0,0 +1,42 @@
|
||||
#!/bin/bash -
|
||||
#title :install_valence.sh
|
||||
#description :This script will install valence package and deploys conf files
|
||||
#author :Intel Corporation
|
||||
#date :21-09-2016
|
||||
#version :0.1
|
||||
#usage :bash mkscript.sh
|
||||
#notes :Run this script as sudo user and not as root.
|
||||
# This script is needed still valence is packaged in to .deb/.rpm
|
||||
#==============================================================================
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
echo $USER
|
||||
|
||||
cd $DIR
|
||||
|
||||
echo "Executing the script inside "
|
||||
pwd
|
||||
|
||||
|
||||
|
||||
# Copy the config files
|
||||
sed s/\${CHUID}/$USER/ $DIR/doc/source/init/valence-api.conf > /tmp/valence-api.conf
|
||||
sudo mv /tmp/valence-api.conf /etc/init/valence-api.conf
|
||||
sed s/\${CHUID}/$USER/ $DIR/doc/source/init/valence-controller.conf > /tmp/valence-controller.conf
|
||||
sudo mv /tmp/valence-controller.conf /etc/init/valence-controller.conf
|
||||
|
||||
# create conf directory for valence
|
||||
sudo mkdir /etc/valence
|
||||
sudo chown ${USER}:${USER} /etc/valence
|
||||
sudo cp etc/valence/valence.conf.sample /etc/valence/valence.conf
|
||||
|
||||
|
||||
# create log directory for valence
|
||||
sudo mkdir /var/log/valence
|
||||
sudo chown ${USER}:${USER} /var/log/valence
|
||||
|
||||
python setup.py install --user
|
||||
|
||||
echo "Installation Completed"
|
||||
echo "To start api : service valence-api start"
|
||||
echo "To start controller : service valence-controller start"
|
0
releasenotes/notes/.placeholder
Normal file
0
releasenotes/notes/.placeholder
Normal file
0
releasenotes/source/_static/.placeholder
Normal file
0
releasenotes/source/_static/.placeholder
Normal file
0
releasenotes/source/_templates/.placeholder
Normal file
0
releasenotes/source/_templates/.placeholder
Normal file
272
releasenotes/source/conf.py
Normal file
272
releasenotes/source/conf.py
Normal file
@ -0,0 +1,272 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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.
|
||||
|
||||
# Plasma Release Notes documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'oslosphinx',
|
||||
'reno.sphinxext',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'plasma Release Notes'
|
||||
copyright = u'2016, Plasma Developers'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = ''
|
||||
# The short X.Y version.
|
||||
version = ''
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
# keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
# html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'PlasmaReleaseNotesdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'PlasmaReleaseNotes.tex', u'Plasma Release Notes Documentation',
|
||||
u'Plasma Developers', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'plasmareleasenotes', u'Plasma Release Notes Documentation',
|
||||
[u'Plasma Developers'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'PlasmaReleaseNotes', u'Plasma Release Notes Documentation',
|
||||
u'Plasma Developers', 'PlasmaReleaseNotes',
|
||||
'Openstack Plasma Project',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
# texinfo_no_detailmenu = False
|
8
releasenotes/source/index.rst
Normal file
8
releasenotes/source/index.rst
Normal file
@ -0,0 +1,8 @@
|
||||
============================================
|
||||
plasma Release Notes
|
||||
============================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
unreleased
|
5
releasenotes/source/unreleased.rst
Normal file
5
releasenotes/source/unreleased.rst
Normal file
@ -0,0 +1,5 @@
|
||||
==============================
|
||||
Current Series Release Notes
|
||||
==============================
|
||||
|
||||
.. release-notes::
|
41
requirements.txt
Normal file
41
requirements.txt
Normal file
@ -0,0 +1,41 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
pbr>=1.6
|
||||
Babel>=2.3.4
|
||||
Paste>=2.0.3
|
||||
PasteDeploy>=1.5.2
|
||||
PyYAML>=3.11
|
||||
WebOb>=1.6.1
|
||||
amqp<=2.0
|
||||
anyjson>=0.3.3
|
||||
argparse>=1.2.1
|
||||
contextlib2>=0.5.3
|
||||
eventlet>=0.19.0
|
||||
greenlet>=0.4.10
|
||||
kombu>=3.0.35
|
||||
logutils>=0.3.3
|
||||
monotonic>=1.1
|
||||
netaddr>=0.7.18
|
||||
netifaces>=0.10.4
|
||||
oslo.concurrency>=3.10.0
|
||||
oslo.config>=3.11.0
|
||||
oslo.context>=2.5.0
|
||||
oslo.i18n>=3.7.0
|
||||
oslo.log>=3.10.0
|
||||
oslo.messaging>=5.4.0
|
||||
oslo.middleware>=3.13.0
|
||||
oslo.reports>=1.11.0
|
||||
oslo.serialization>=2.9.0
|
||||
oslo.service>=1.12.0
|
||||
oslo.utils>=3.13.0
|
||||
oslo.versionedobjects>=1.12.0
|
||||
pecan>=1.1.1
|
||||
requests>=2.10.0
|
||||
six>=1.10.0
|
||||
stevedore>=1.15.0
|
||||
waitress>=0.9.0
|
||||
wrapt>=1.10.8
|
||||
wsgiref>=0.1.2
|
||||
|
59
setup.cfg
Normal file
59
setup.cfg
Normal file
@ -0,0 +1,59 @@
|
||||
[metadata]
|
||||
name = valence
|
||||
summary = Openstack Valence Project
|
||||
description-file =
|
||||
README.rst
|
||||
author = Intel Corporation
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = https://launchpad.net/plasma
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
valence
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[compile_catalog]
|
||||
directory = valence/locale
|
||||
domain = valence
|
||||
|
||||
[update_catalog]
|
||||
domain = valence
|
||||
output_dir = valence/locale
|
||||
input_file = valence/locale/valence.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = valence/locale/valence.pot
|
||||
|
||||
[build_releasenotes]
|
||||
all_files = 1
|
||||
build-dir = releasenotes/build
|
||||
source-dir = releasenotes/source
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
valence-api = valence.cmd.api:main
|
||||
valence-controller = valence.cmd.controller:main
|
||||
|
||||
oslo.config.opts =
|
||||
valence = valence.api.config:list_opts
|
29
setup.py
Normal file
29
setup.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
17
test-requirements.txt
Normal file
17
test-requirements.txt
Normal file
@ -0,0 +1,17 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking<0.11,>=0.10.0
|
||||
|
||||
coverage>=3.6
|
||||
python-subunit>=0.0.18
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
|
||||
# releasenotes
|
||||
reno>=1.6.2 # Apache2
|
64
tox.ini
Normal file
64
tox.ini
Normal file
@ -0,0 +1,64 @@
|
||||
[tox]
|
||||
minversion = 2.0
|
||||
envlist = py34-constraints,py27-constraints,pep8-constraints
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command =
|
||||
constraints: {[testenv:common-constraints]install_command}
|
||||
pip install -U {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py test --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:common-constraints]
|
||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8 {posargs}
|
||||
|
||||
[testenv:pep8-constraints]
|
||||
install_command = {[testenv:common-constraints]install_command}
|
||||
commands = flake8 {posargs}
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:venv-constraints]
|
||||
install_command = {[testenv:common-constraints]install_command}
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py test --coverage --testr-args='{posargs}'
|
||||
|
||||
[testenv:cover-constraints]
|
||||
install_command = {[testenv:common-constraints]install_command}
|
||||
commands = python setup.py test --coverage --testr-args='{posargs}'
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:releasenotes]
|
||||
commands =
|
||||
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||
|
||||
[testenv:docs-constraints]
|
||||
install_command = {[testenv:common-constraints]install_command}
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper {posargs}
|
||||
|
||||
[testenv:debug-constraints]
|
||||
install_command = {[testenv:common-constraints]install_command}
|
||||
commands = oslo_debug_helper {posargs}
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
0
valence/__init__.py
Normal file
0
valence/__init__.py
Normal file
0
valence/api/__init__.py
Normal file
0
valence/api/__init__.py
Normal file
61
valence/api/app.py
Normal file
61
valence/api/app.py
Normal file
@ -0,0 +1,61 @@
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_middleware import request_id
|
||||
from oslo_service import service
|
||||
from pecan import configuration
|
||||
from pecan import make_app
|
||||
from valence.api import hooks
|
||||
from valence.common import exceptions as p_excp
|
||||
|
||||
def setup_app(*args, **kwargs):
|
||||
config = {
|
||||
'server': {
|
||||
'host': cfg.CONF.api.bind_port,
|
||||
'port': cfg.CONF.api.bind_host
|
||||
},
|
||||
'app': {
|
||||
'root': 'valence.api.controllers.root.RootController',
|
||||
'modules': ['valence.api'],
|
||||
'errors': {
|
||||
400: '/error',
|
||||
'__force_dict__': True
|
||||
}
|
||||
}
|
||||
}
|
||||
pecan_config = configuration.conf_from_dict(config)
|
||||
|
||||
app_hooks = [hooks.CORSHook()]
|
||||
|
||||
app = make_app(
|
||||
pecan_config.app.root,
|
||||
hooks=app_hooks,
|
||||
force_canonical = False,
|
||||
logging=getattr(config, 'logging', {})
|
||||
)
|
||||
return app
|
||||
|
||||
|
||||
_launcher = None
|
||||
|
||||
|
||||
def serve(api_service, conf, workers=1):
|
||||
global _launcher
|
||||
if _launcher:
|
||||
raise RuntimeError('serve() can only be called once')
|
||||
|
||||
_launcher = service.launch(conf, api_service, workers=workers)
|
||||
|
||||
|
||||
def wait():
|
||||
_launcher.wait()
|
66
valence/api/config.py
Normal file
66
valence/api/config.py
Normal file
@ -0,0 +1,66 @@
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from valence.common import rpc
|
||||
import sys
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
common_opts = [
|
||||
cfg.StrOpt('auth_strategy', default='noauth',
|
||||
help=("The type of authentication to use")),
|
||||
cfg.BoolOpt('allow_pagination', default=False,
|
||||
help=("Allow the usage of the pagination")),
|
||||
cfg.BoolOpt('allow_sorting', default=False,
|
||||
help=("Allow the usage of the sorting")),
|
||||
cfg.StrOpt('pagination_max_limit', default="-1",
|
||||
help=("The maximum number of items returned in a single "
|
||||
"response, value was 'infinite' or negative integer "
|
||||
"means no limit")),
|
||||
]
|
||||
|
||||
api_opts = [
|
||||
cfg.StrOpt('bind_host', default='0.0.0.0',
|
||||
help=("The host IP to bind to")),
|
||||
cfg.IntOpt('bind_port', default=8181,
|
||||
help=("The port to bind to")),
|
||||
cfg.IntOpt('api_workers', default=2,
|
||||
help=("number of api workers"))
|
||||
]
|
||||
|
||||
|
||||
def init(args, **kwargs):
|
||||
# Register the configuration options
|
||||
api_conf_group = cfg.OptGroup(name='api', title='Valence API options')
|
||||
cfg.CONF.register_group(api_conf_group)
|
||||
cfg.CONF.register_opts(api_opts, group=api_conf_group)
|
||||
cfg.CONF.register_opts(common_opts)
|
||||
logging.register_options(cfg.CONF)
|
||||
|
||||
cfg.CONF(args=args, project='valence',
|
||||
**kwargs)
|
||||
|
||||
rpc.init(cfg.CONF)
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Sets up the logging options for a log with supplied name."""
|
||||
product_name = "valence"
|
||||
logging.setup(cfg.CONF, product_name)
|
||||
LOG.info("Logging enabled!")
|
||||
LOG.debug("command line: %s", " ".join(sys.argv))
|
||||
|
||||
|
||||
def list_opts():
|
||||
yield None, common_opts
|
0
valence/api/controllers/__init__.py
Normal file
0
valence/api/controllers/__init__.py
Normal file
35
valence/api/controllers/base.py
Normal file
35
valence/api/controllers/base.py
Normal file
@ -0,0 +1,35 @@
|
||||
# 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.
|
||||
|
||||
|
||||
class APIBase(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for field in self.fields:
|
||||
if field in kwargs:
|
||||
value = kwargs[field]
|
||||
setattr(self, field, value)
|
||||
|
||||
def __setattr__(self, field, value):
|
||||
if field in self.fields:
|
||||
validator = self.fields[field]['validate']
|
||||
value = validator(value)
|
||||
super(APIBase, self).__setattr__(field, value)
|
||||
|
||||
def as_dict(self):
|
||||
"""Render this object as a dict of its fields."""
|
||||
return {f: getattr(self, f)
|
||||
for f in self.fields
|
||||
if hasattr(self, f)}
|
||||
|
||||
def __json__(self):
|
||||
return self.as_dict()
|
56
valence/api/controllers/link.py
Normal file
56
valence/api/controllers/link.py
Normal file
@ -0,0 +1,56 @@
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
import pecan
|
||||
from valence.api.controllers import base
|
||||
from valence.api.controllers import types
|
||||
|
||||
|
||||
def build_url(resource, resource_args, bookmark=False, base_url=None):
|
||||
if base_url is None:
|
||||
base_url = pecan.request.host_url
|
||||
|
||||
template = '%(url)s/%(res)s' if bookmark else '%(url)s/v1/%(res)s'
|
||||
# FIXME(lucasagomes): I'm getting a 404 when doing a GET on
|
||||
# a nested resource that the URL ends with a '/'.
|
||||
# https://groups.google.com/forum/#!topic/pecan-dev/QfSeviLg5qs
|
||||
template += '%(args)s' if resource_args.startswith('?') else '/%(args)s'
|
||||
return template % {'url': base_url, 'res': resource, 'args': resource_args}
|
||||
|
||||
|
||||
class Link(base.APIBase):
|
||||
"""A link representation."""
|
||||
|
||||
fields = {
|
||||
'href': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'rel': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'type': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def make_link(rel_name, url, resource, resource_args,
|
||||
bookmark=False, type=None):
|
||||
href = build_url(resource, resource_args,
|
||||
bookmark=bookmark, base_url=url)
|
||||
if type is None:
|
||||
return Link(href=href, rel=rel_name)
|
||||
else:
|
||||
return Link(href=href, rel=rel_name, type=type)
|
78
valence/api/controllers/root.py
Normal file
78
valence/api/controllers/root.py
Normal file
@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from pecan import route
|
||||
from valence.api.controllers import base
|
||||
from valence.api.controllers import link
|
||||
from valence.api.controllers import types
|
||||
from valence.api.controllers.v1 import controller as v1controller
|
||||
|
||||
|
||||
class Version(base.APIBase):
|
||||
"""An API version representation."""
|
||||
|
||||
fields = {
|
||||
'id': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'links': {
|
||||
'validate': types.List(types.Custom(link.Link)).validate
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def convert(id):
|
||||
version = Version()
|
||||
version.id = id
|
||||
version.links = [link.Link.make_link('self', request.host_url,
|
||||
id, '', bookmark=True)]
|
||||
return version
|
||||
|
||||
|
||||
class Root(base.APIBase):
|
||||
|
||||
fields = {
|
||||
'id': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'description': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'versions': {
|
||||
'validate': types.List(types.Custom(Version)).validate
|
||||
},
|
||||
'default_version': {
|
||||
'validate': types.Custom(Version).validate
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
root = Root()
|
||||
root.name = "OpenStack Valence API"
|
||||
root.description = ("Valence is an OpenStack project")
|
||||
root.versions = [Version.convert('v1')]
|
||||
root.default_version = Version.convert('v1')
|
||||
return root
|
||||
|
||||
|
||||
class RootController(object):
|
||||
@expose('json')
|
||||
def index(self):
|
||||
return Root.convert()
|
||||
|
||||
route(RootController, 'v1', v1controller.V1Controller())
|
132
valence/api/controllers/types.py
Normal file
132
valence/api/controllers/types.py
Normal file
@ -0,0 +1,132 @@
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import six
|
||||
from oslo_utils import strutils
|
||||
from valence.common import exceptions as exception
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Text(object):
|
||||
type_name = 'Text'
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
if not isinstance(value, six.string_types):
|
||||
raise exception.InvalidValue(value=value, type=cls.type_name)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class String(object):
|
||||
type_name = 'String'
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value, min_length=0, max_length=None):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
strutils.check_string_length(value, min_length=min_length,
|
||||
max_length=max_length)
|
||||
except TypeError:
|
||||
raise exception.InvalidValue(value=value, type=cls.type_name)
|
||||
except ValueError as e:
|
||||
raise exception.InvalidValue(message=str(e))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Integer(object):
|
||||
type_name = 'Integer'
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value, minimum=None):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
if not isinstance(value, six.integer_types):
|
||||
try:
|
||||
value = int(value)
|
||||
except Exception:
|
||||
LOG.exception('Failed to convert value to int')
|
||||
raise exception.InvalidValue(value=value, type=cls.type_name)
|
||||
|
||||
if minimum is not None and value < minimum:
|
||||
message = _("Integer '%(value)s' is smaller than "
|
||||
"'%(min)d'.") % {'value': value, 'min': minimum}
|
||||
raise exception.InvalidValue(message=message)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Bool(object):
|
||||
type_name = 'Bool'
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value, default=None):
|
||||
if value is None:
|
||||
value = default
|
||||
|
||||
if not isinstance(value, bool):
|
||||
try:
|
||||
value = strutils.bool_from_string(value, strict=True)
|
||||
except Exception:
|
||||
LOG.exception('Failed to convert value to bool')
|
||||
raise exception.InvalidValue(value=value, type=cls.type_name)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Custom(object):
|
||||
def __init__(self, user_class):
|
||||
super(Custom, self).__init__()
|
||||
self.user_class = user_class
|
||||
self.type_name = self.user_class.__name__
|
||||
|
||||
def validate(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
if not isinstance(value, self.user_class):
|
||||
try:
|
||||
value = self.user_class(**value)
|
||||
except Exception:
|
||||
LOG.exception('Failed to validate received value')
|
||||
raise exception.InvalidValue(value=value, type=self.type_name)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class List(object):
|
||||
def __init__(self, type):
|
||||
super(List, self).__init__()
|
||||
self.type = type
|
||||
self.type_name = 'List(%s)' % self.type.type_name
|
||||
|
||||
def validate(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
if not isinstance(value, list):
|
||||
raise exception.InvalidValue(value=value, type=self.type_name)
|
||||
|
||||
try:
|
||||
return [self.type.validate(v) for v in value]
|
||||
except Exception:
|
||||
LOG.exception('Failed to validate received value')
|
||||
raise exception.InvalidValue(value=value, type=self.type_name)
|
0
valence/api/controllers/v1/__init__.py
Normal file
0
valence/api/controllers/v1/__init__.py
Normal file
84
valence/api/controllers/v1/controller.py
Normal file
84
valence/api/controllers/v1/controller.py
Normal file
@ -0,0 +1,84 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from pecan import route
|
||||
from valence.api.controllers import base
|
||||
from valence.api.controllers import link
|
||||
from valence.api.controllers import types
|
||||
from valence.api.controllers.v1 import flavor as v1flavor
|
||||
from valence.api.controllers.v1 import nodes as v1nodes
|
||||
|
||||
|
||||
class MediaType(base.APIBase):
|
||||
"""A media type representation."""
|
||||
|
||||
fields = {
|
||||
'base': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'type': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class V1(base.APIBase):
|
||||
"""The representation of the version 1 of the API."""
|
||||
|
||||
fields = {
|
||||
'id': {
|
||||
'validate': types.Text.validate
|
||||
},
|
||||
'media_types': {
|
||||
'validate': types.List(types.Custom(MediaType)).validate
|
||||
},
|
||||
'links': {
|
||||
'validate': types.List(types.Custom(link.Link)).validate
|
||||
},
|
||||
'services': {
|
||||
'validate': types.List(types.Custom(link.Link)).validate
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
v1 = V1()
|
||||
v1.id = "v1"
|
||||
v1.links = [link.Link.make_link('self', request.host_url,
|
||||
'v1', '', bookmark=True),
|
||||
link.Link.make_link('describedby',
|
||||
'http://docs.openstack.org',
|
||||
'developer/valence/dev',
|
||||
'api-spec-v1.html',
|
||||
bookmark=True, type='text/html')]
|
||||
v1.media_types = [MediaType(base='application/json',
|
||||
type='application/vnd.openstack.valence.v1+json')]
|
||||
v1.services = [link.Link.make_link('self', request.host_url,
|
||||
'services', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
request.host_url,
|
||||
'services', '',
|
||||
bookmark=True)]
|
||||
return v1
|
||||
|
||||
|
||||
class V1Controller(object):
|
||||
@expose('json')
|
||||
def index(self):
|
||||
return V1.convert()
|
||||
|
||||
route(V1Controller, 'flavor', v1flavor.FlavorController())
|
||||
route(V1Controller, 'nodes', v1nodes.NodesController())
|
44
valence/api/controllers/v1/flavor.py
Normal file
44
valence/api/controllers/v1/flavor.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from valence.controller import api as controller_api
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FlavorController(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FlavorController, self).__init__(*args, **kwargs)
|
||||
|
||||
# HTTP GET /flavor/
|
||||
@expose(generic=True, template='json')
|
||||
def index(self):
|
||||
LOG.debug("GET /flavor")
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
res = rpcapi.flavor_options()
|
||||
return res
|
||||
|
||||
# HTTP POST /flavor/
|
||||
@index.when(method='POST', template='json')
|
||||
def index_POST(self, **kw):
|
||||
LOG.debug("POST /flavor")
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
res = rpcapi.flavor_generate(criteria=kw['criteria'])
|
||||
return res
|
84
valence/api/controllers/v1/nodes.py
Normal file
84
valence/api/controllers/v1/nodes.py
Normal file
@ -0,0 +1,84 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from pecan import response
|
||||
from pecan.rest import RestController
|
||||
from valence.controller import api as controller_api
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
#class NodeDetailController(object):
|
||||
class NodeDetailController(RestController):
|
||||
def __init__(self, nodeid):
|
||||
self.nodeid = nodeid
|
||||
|
||||
# HTTP GET /nodes/
|
||||
@expose()
|
||||
def delete(self):
|
||||
LOG.debug("DELETE /nodes")
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
res = rpcapi.delete_composednode(nodeid=self.nodeid)
|
||||
LOG.info(str(res))
|
||||
return res
|
||||
|
||||
@expose()
|
||||
def storages(self):
|
||||
pecan.abort(501, "/nodes/node id/storages")
|
||||
|
||||
|
||||
class NodesController(RestController):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NodesController, self).__init__(*args, **kwargs)
|
||||
|
||||
# HTTP GET /nodes/
|
||||
@expose(template='json')
|
||||
def get_all(self, **kwargs):
|
||||
LOG.debug("GET /nodes")
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
res = rpcapi.list_nodes(filters=kwargs)
|
||||
return res
|
||||
|
||||
# HTTP GET /nodes/
|
||||
# @index.when(method='POST', template='json')
|
||||
@expose(template='json')
|
||||
def post(self, **kwargs):
|
||||
LOG.debug("POST /nodes")
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
res = rpcapi.compose_nodes(criteria=kwargs)
|
||||
return res
|
||||
|
||||
@expose(template='json')
|
||||
def get(self, nodeid):
|
||||
LOG.debug("GET /nodes" + nodeid)
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
node = rpcapi.get_nodebyid(nodeid=nodeid)
|
||||
if not node:
|
||||
pecan.abort(404)
|
||||
return node
|
||||
|
||||
@expose()
|
||||
def _lookup(self, nodeid, *remainder):
|
||||
# node = get_student_by_primary_key(primary_key)
|
||||
if nodeid:
|
||||
return NodeDetailController(nodeid), remainder
|
||||
else:
|
||||
pecan.abort(404)
|
44
valence/api/controllers/v1/storages.py
Normal file
44
valence/api/controllers/v1/storages.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from valence.controller import api as controller_api
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StoragesController(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(StoragesController, self).__init__(*args, **kwargs)
|
||||
|
||||
# HTTP GET /storages/
|
||||
@expose(generic=True, template='json')
|
||||
def index(self):
|
||||
LOG.debug("GET /storages")
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
LOG.debug(rpcapi)
|
||||
pecan.abort(501, "GET /storages is Not yet implemented")
|
||||
|
||||
@expose(template='json')
|
||||
def get(self, storageid):
|
||||
LOG.debug("GET /storages" + storageid)
|
||||
rpcapi = controller_api.API(context=request.context)
|
||||
LOG.debug(rpcapi)
|
||||
pecan.abort(501, "GET /storages/storage is Not yet implemented")
|
13
valence/api/hooks.py
Normal file
13
valence/api/hooks.py
Normal file
@ -0,0 +1,13 @@
|
||||
from oslo_config import cfg
|
||||
from pecan.hooks import PecanHook
|
||||
|
||||
|
||||
class CORSHook(PecanHook):
|
||||
|
||||
def after(self, state):
|
||||
state.response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
state.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, DELETE, PUT, LIST, OPTIONS'
|
||||
state.response.headers['Access-Control-Allow-Headers'] = 'origin, authorization, content-type, accept'
|
||||
if not state.response.headers['Content-Length']:
|
||||
state.response.headers['Content-Length'] = str(len(state.response.body))
|
||||
|
0
valence/cmd/__init__.py
Normal file
0
valence/cmd/__init__.py
Normal file
49
valence/cmd/api.py
Executable file
49
valence/cmd/api.py
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import wsgi
|
||||
|
||||
from valence.api import app
|
||||
from valence.api import config as api_config
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger('valence.api')
|
||||
|
||||
|
||||
def main():
|
||||
api_config.init(sys.argv[1:])
|
||||
api_config.setup_logging()
|
||||
application = app.setup_app()
|
||||
host = CONF.api.bind_host
|
||||
port = CONF.api.bind_port
|
||||
workers = 1
|
||||
|
||||
LOG.info(("Server on http://%(host)s:%(port)s with %(workers)s"),
|
||||
{'host': host, 'port': port, 'workers': workers})
|
||||
|
||||
service = wsgi.Server(CONF, "valence", application, host, port)
|
||||
|
||||
app.serve(service, CONF, workers)
|
||||
|
||||
LOG.info("Configuration:")
|
||||
app.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
56
valence/cmd/controller.py
Normal file
56
valence/cmd/controller.py
Normal file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Starter script for the Valence controller service."""
|
||||
|
||||
import os
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import service
|
||||
from valence.common import rpc_service
|
||||
from valence.controller import config as controller_config
|
||||
from valence.controller.handlers import flavor_controller
|
||||
from valence.controller.handlers import node_controller
|
||||
# from valence import version
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
controller_config.init(sys.argv[1:])
|
||||
controller_config.setup_logging()
|
||||
LOG.info(('Starting valence-controller in PID %s'), os.getpid())
|
||||
LOG.debug("Configuration:")
|
||||
# cfg.CONF.import_opt('topic',
|
||||
# 'valence.controller.config',
|
||||
# group='controller')
|
||||
|
||||
controller_id = uuid.uuid4()
|
||||
endpoints = [
|
||||
flavor_controller.Handler(),
|
||||
node_controller.Handler()
|
||||
]
|
||||
|
||||
server = rpc_service.Service.create(cfg.CONF.controller.topic,
|
||||
controller_id, endpoints,
|
||||
binary='valence-controller')
|
||||
launcher = service.launch(cfg.CONF, server)
|
||||
launcher.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
0
valence/common/__init__.py
Normal file
0
valence/common/__init__.py
Normal file
75
valence/common/context.py
Normal file
75
valence/common/context.py
Normal file
@ -0,0 +1,75 @@
|
||||
# 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.
|
||||
|
||||
from oslo_context import context as oslo_ctx
|
||||
|
||||
|
||||
class ContextBase(oslo_ctx.RequestContext):
|
||||
def __init__(self, auth_token=None, user_id=None, tenant_id=None,
|
||||
is_admin=False, request_id=None, overwrite=True,
|
||||
user_name=None, tenant_name=None, auth_url=None,
|
||||
region=None, password=None, domain='default',
|
||||
project_name=None, **kwargs):
|
||||
super(ContextBase, self).__init__(
|
||||
auth_token=auth_token,
|
||||
user=user_id or kwargs.get('user', None),
|
||||
tenant=tenant_id or kwargs.get('tenant', None),
|
||||
domain=kwargs.get('domain', None),
|
||||
user_domain=kwargs.get('user_domain', None),
|
||||
project_domain=kwargs.get('project_domain', None),
|
||||
is_admin=is_admin,
|
||||
read_only=kwargs.get('read_only', False),
|
||||
show_deleted=kwargs.get('show_deleted', False),
|
||||
request_id=request_id,
|
||||
resource_uuid=kwargs.get('resource_uuid', None),
|
||||
overwrite=overwrite)
|
||||
self.user_name = user_name
|
||||
self.tenant_name = tenant_name
|
||||
self.tenant_id = tenant_id
|
||||
self.auth_url = auth_url
|
||||
self.password = password
|
||||
self.default_name = domain
|
||||
self.region_name = region
|
||||
self.project_name = project_name
|
||||
|
||||
def to_dict(self):
|
||||
ctx_dict = super(ContextBase, self).to_dict()
|
||||
# ctx_dict.update({
|
||||
# to do : dict update
|
||||
# })
|
||||
return ctx_dict
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, ctx):
|
||||
return cls(**ctx)
|
||||
|
||||
|
||||
class Context(ContextBase):
|
||||
def __init__(self, **kwargs):
|
||||
super(Context, self).__init__(**kwargs)
|
||||
self._session = None
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
return self._session
|
||||
|
||||
|
||||
def get_admin_context(read_only=True):
|
||||
return ContextBase(user_id=None,
|
||||
project_id=None,
|
||||
is_admin=True,
|
||||
overwrite=False,
|
||||
read_only=read_only)
|
||||
|
||||
|
||||
def get_current():
|
||||
return oslo_ctx.get_current()
|
79
valence/common/exceptions.py
Normal file
79
valence/common/exceptions.py
Normal file
@ -0,0 +1,79 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
RSC base exception handling.
|
||||
"""
|
||||
import six
|
||||
|
||||
from oslo_utils import excutils
|
||||
|
||||
|
||||
class RSCException(Exception):
|
||||
"""Base RSC Exception."""
|
||||
|
||||
message = "An unknown exception occurred."
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
try:
|
||||
super(RSCException, self).__init__(self.message % kwargs)
|
||||
self.msg = self.message % kwargs
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
if not self.use_fatal_exceptions():
|
||||
ctxt.reraise = False
|
||||
# at least get the core message out if something happened
|
||||
super(RSCException, self).__init__(self.message)
|
||||
|
||||
if six.PY2:
|
||||
def __unicode__(self):
|
||||
return unicode(self.msg)
|
||||
|
||||
def use_fatal_exceptions(self):
|
||||
return False
|
||||
|
||||
|
||||
class BadRequest(RSCException):
|
||||
message = 'Bad %(resource)s request'
|
||||
|
||||
|
||||
class NotImplemented(RSCException):
|
||||
message = ("Not yet implemented in RSC %(func_name)s: ")
|
||||
|
||||
|
||||
class NotFound(RSCException):
|
||||
message = ("URL not Found")
|
||||
|
||||
|
||||
class Conflict(RSCException):
|
||||
pass
|
||||
|
||||
|
||||
class ServiceUnavailable(RSCException):
|
||||
message = "The service is unavailable"
|
||||
|
||||
|
||||
class ConnectionRefused(RSCException):
|
||||
message = "Connection to the service endpoint is refused"
|
||||
|
||||
|
||||
class TimeOut(RSCException):
|
||||
message = "Timeout when connecting to OpenStack Service"
|
||||
|
||||
|
||||
class InternalError(RSCException):
|
||||
message = "Error when performing operation"
|
||||
|
||||
|
||||
class InvalidInputError(RSCException):
|
||||
message = ("An invalid value was provided for %(opt_name)s: "
|
||||
"%(opt_value)s")
|
97
valence/common/osinterface.py
Normal file
97
valence/common/osinterface.py
Normal file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
import json
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
cfg.CONF.import_group('undercloud', 'valence.controller.config')
|
||||
|
||||
|
||||
def _send_request(url, method, headers, requestbody=None):
|
||||
defaultheaders = {'Content-Type': 'application/json'}
|
||||
auth = HTTPBasicAuth(cfg.CONF.undercloud.os_user,
|
||||
cfg.CONF.undercloud.os_password)
|
||||
headers = defaultheaders.update(headers)
|
||||
LOG.debug(url)
|
||||
resp = requests.request(method,
|
||||
url,
|
||||
headers=defaultheaders,
|
||||
data=requestbody,
|
||||
auth=auth)
|
||||
LOG.debug(resp.status_code)
|
||||
return resp.json()
|
||||
|
||||
|
||||
def _get_servicecatalogue_endpoint(keystonejson, servicename):
|
||||
"""Fetch particular endpoint from Keystone.
|
||||
|
||||
This function is to get the particular endpoint from the
|
||||
list of endpoints returned fro keystone.
|
||||
|
||||
"""
|
||||
|
||||
for d in keystonejson["access"]["serviceCatalog"]:
|
||||
if(d["name"] == servicename):
|
||||
return d["endpoints"][0]["publicURL"]
|
||||
|
||||
|
||||
def _get_token_and_url(nameofendpoint):
|
||||
"""Fetch token from the endpoint
|
||||
|
||||
This function get new token and associated endpoint.
|
||||
name of endpoint carries the name of the service whose
|
||||
endpoint need to be found.
|
||||
|
||||
"""
|
||||
|
||||
url = cfg.CONF.undercloud.os_admin_url + "/tokens"
|
||||
data = {"auth":
|
||||
{"tenantName": cfg.CONF.undercloud.os_tenant,
|
||||
"passwordCredentials":
|
||||
{"username": cfg.CONF.undercloud.os_user,
|
||||
"password": cfg.CONF.undercloud.os_password}}}
|
||||
rdata = _send_request(url, "POST", {}, json.dumps(data))
|
||||
tokenid = rdata["access"]["token"]["id"]
|
||||
endpoint = _get_servicecatalogue_endpoint(rdata, nameofendpoint)
|
||||
LOG.debug("Token,Endpoint %s: %s from keystone for %s"
|
||||
% (tokenid, endpoint, nameofendpoint))
|
||||
return (tokenid, endpoint)
|
||||
|
||||
|
||||
# put this function in utils.py later
|
||||
def _get_imageid(jsondata, imgname):
|
||||
# write a generic funciton for this and _get_servicecatalogue_endpoint
|
||||
for d in jsondata["images"]:
|
||||
if(d["name"] == imgname):
|
||||
return d["id"]
|
||||
|
||||
|
||||
def get_undercloud_images():
|
||||
tokenid, endpoint = _get_token_and_url("glance")
|
||||
resp = _send_request(endpoint + "/v2/images",
|
||||
"GET",
|
||||
{'X-Auth-Token': tokenid})
|
||||
imagemap = {"deploy_ramdisk": _get_imageid(resp, "bm-deploy-ramdisk"),
|
||||
"deploy_kernel": _get_imageid(resp, "bm-deploy-kernel"),
|
||||
"image_source": _get_imageid(resp, "overcloud-full"),
|
||||
"ramdisk": _get_imageid(resp, "overcloud-full-initrd"),
|
||||
"kernel": _get_imageid(resp, "overcloud-full-vmlinuz")}
|
||||
return imagemap
|
0
valence/common/redfish/__init__.py
Normal file
0
valence/common/redfish/__init__.py
Normal file
417
valence/common/redfish/api.py
Normal file
417
valence/common/redfish/api.py
Normal file
@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import json
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from valence.common.redfish import tree
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
cfg.CONF.import_group('podm', 'valence.common.redfish.config')
|
||||
|
||||
|
||||
def get_rfs_url(serviceext):
|
||||
REDFISH_BASE_EXT = "/redfish/v1/"
|
||||
INDEX = ''
|
||||
# '/index.json'
|
||||
if REDFISH_BASE_EXT in serviceext:
|
||||
return cfg.CONF.podm.url + serviceext + INDEX
|
||||
else:
|
||||
return cfg.CONF.podm.url + REDFISH_BASE_EXT + serviceext + INDEX
|
||||
|
||||
|
||||
def send_request(resource, method="GET",**kwargs):
|
||||
# The verify=false param in the request should be removed eventually
|
||||
url = get_rfs_url(resource)
|
||||
httpuser = cfg.CONF.podm.user
|
||||
httppwd = cfg.CONF.podm.password
|
||||
resp = None
|
||||
try:
|
||||
resp = requests.request(method, url, verify=False, auth=HTTPBasicAuth(httpuser, httppwd), **kwargs)
|
||||
except requests.exceptions.RequestException as e:
|
||||
LOG.error(e)
|
||||
return resp
|
||||
|
||||
|
||||
def filter_chassis(jsonContent, filterCondition):
|
||||
returnJSONObj = {}
|
||||
returnMembers = []
|
||||
parsed = json.loads(jsonContent)
|
||||
members = parsed['Members']
|
||||
# count = parsed['Members@odata.count']
|
||||
for member in members:
|
||||
resource = member['@odata.id']
|
||||
resp = send_request(resource)
|
||||
memberJsonObj = json.loads(resp.json())
|
||||
chassisType = memberJsonObj['ChassisType']
|
||||
if chassisType == filterCondition:
|
||||
returnMembers.append(member)
|
||||
returnJSONObj["Members"] = returnMembers
|
||||
returnJSONObj["Members@odata.count"] = len(returnMembers)
|
||||
return returnJSONObj
|
||||
|
||||
|
||||
def generic_filter(jsonContent, filterConditions):
|
||||
# returns boolean based on filters..its generic filter
|
||||
# returnMembers = []
|
||||
is_filter_passed = False
|
||||
for fc in filterConditions:
|
||||
if fc in jsonContent:
|
||||
if jsonContent[fc].lower() == filterConditions[fc].lower():
|
||||
is_filter_passed = True
|
||||
else:
|
||||
is_filter_passed = False
|
||||
break
|
||||
elif "/" in fc:
|
||||
querylst = fc.split("/")
|
||||
tmp = jsonContent
|
||||
for q in querylst:
|
||||
tmp = tmp[q]
|
||||
if tmp.lower() == filterConditions[fc].lower():
|
||||
is_filter_passed = True
|
||||
else:
|
||||
is_filter_passed = False
|
||||
break
|
||||
else:
|
||||
LOG.warn(" Filter string mismatch ")
|
||||
LOG.info(" JSON CONTENT " + str(is_filter_passed))
|
||||
return is_filter_passed
|
||||
|
||||
|
||||
def get_details(source):
|
||||
# count = source['Members@odata.count']
|
||||
returnJSONObj = []
|
||||
members = source['Members']
|
||||
for member in members:
|
||||
resource = member['@odata.id']
|
||||
resp = send_request(resource)
|
||||
memberJson = resp.json()
|
||||
memberJsonObj = json.loads(memberJson)
|
||||
returnJSONObj[resource] = memberJsonObj
|
||||
return returnJSONObj
|
||||
|
||||
|
||||
def systemdetails():
|
||||
returnJSONObj = []
|
||||
parsed = send_request('Systems')
|
||||
members = parsed['Members']
|
||||
for member in members:
|
||||
resource = member['@odata.id']
|
||||
resp = send_request(resource)
|
||||
memberJsonContent = resp.json()
|
||||
memberJSONObj = json.loads(memberJsonContent)
|
||||
returnJSONObj[resource] = memberJSONObj
|
||||
return(json.dumps(returnJSONObj))
|
||||
|
||||
|
||||
def nodedetails():
|
||||
returnJSONObj = []
|
||||
parsed = send_request('Nodes')
|
||||
members = parsed['Members']
|
||||
for member in members:
|
||||
resource = member['@odata.id']
|
||||
resp = send_request(resource)
|
||||
memberJSONObj = resp.json()
|
||||
returnJSONObj[resource] = memberJSONObj
|
||||
return(json.dumps(returnJSONObj))
|
||||
|
||||
|
||||
def podsdetails():
|
||||
jsonContent = send_request('Chassis')
|
||||
pods = filter_chassis(jsonContent, 'Pod')
|
||||
podsDetails = get_details(pods)
|
||||
return json.dumps(podsDetails)
|
||||
|
||||
|
||||
def racksdetails():
|
||||
jsonContent = send_request('Chassis')
|
||||
racks = filter_chassis(jsonContent, 'Rack')
|
||||
racksDetails = get_details(racks)
|
||||
return json.dumps(racksDetails)
|
||||
|
||||
|
||||
def racks():
|
||||
jsonContent = send_request('Chassis')
|
||||
racks = filter_chassis(jsonContent, 'Rack')
|
||||
return json.dumps(racks)
|
||||
|
||||
|
||||
def pods():
|
||||
jsonContent = send_request('Chassis')
|
||||
pods = filter_chassis(jsonContent, 'Pod')
|
||||
return json.dumps(pods)
|
||||
|
||||
|
||||
def urls2list(url):
|
||||
# This will extract the url values from @odata.id inside Members
|
||||
resp = send_request(url)
|
||||
respdata = resp.json()
|
||||
return [u['@odata.id'] for u in respdata['Members']]
|
||||
|
||||
|
||||
def extract_val(data, path):
|
||||
# function to select value at particularpath
|
||||
patharr = path.split("/")
|
||||
for p in patharr:
|
||||
data = data[p]
|
||||
return data
|
||||
|
||||
|
||||
def node_cpu_details(nodeurl):
|
||||
cpucnt = 0
|
||||
cpuarch = ""
|
||||
cpulist = urls2list(nodeurl + '/Processors')
|
||||
for lnk in cpulist:
|
||||
LOG.info("Processing CPU %s" % lnk)
|
||||
resp = send_request(lnk)
|
||||
respdata = resp.json()
|
||||
cpucnt += extract_val(respdata, "TotalCores")
|
||||
cpuarch = extract_val(respdata, "InstructionSet")
|
||||
cpumodel = extract_val(respdata, "Model")
|
||||
LOG.debug(" Cpu details %s: %d: %s: %s "
|
||||
% (nodeurl, cpucnt, cpuarch, cpumodel))
|
||||
return {"count": str(cpucnt), "arch": cpuarch, "model": cpumodel}
|
||||
|
||||
|
||||
def node_ram_details(nodeurl):
|
||||
# this extracts the RAM and returns as dictionary
|
||||
resp = send_request(nodeurl)
|
||||
respjson = resp.json()
|
||||
ram = extract_val(respjson, "MemorySummary/TotalSystemMemoryGiB")
|
||||
#LOG.debug(" Total Ram for node %s : %d " % (nodeurl, ram))
|
||||
return str(ram) if ram else "0"
|
||||
|
||||
|
||||
def node_nw_details(nodeurl):
|
||||
# this extracts the total nw interfaces and returns as a string
|
||||
resp = send_request(nodeurl + "/EthernetInterfaces")
|
||||
respbody = resp.json()
|
||||
nwi = extract_val(respbody, "Members@odata.count")
|
||||
LOG.debug(" Total NW for node %s : %d " % (nodeurl, nwi))
|
||||
return str(nwi) if nwi else "0"
|
||||
|
||||
|
||||
def node_storage_details(nodeurl):
|
||||
# this extracts the RAM and returns as dictionary
|
||||
storagecnt = 0
|
||||
hddlist = urls2list(nodeurl + "/SimpleStorage")
|
||||
for lnk in hddlist:
|
||||
resp = send_request(lnk)
|
||||
respbody = resp.json()
|
||||
hdds = extract_val(respbody, "Devices")
|
||||
for sd in hdds:
|
||||
if "CapacityBytes" in sd:
|
||||
if sd["CapacityBytes"] is not None:
|
||||
storagecnt += sd["CapacityBytes"]
|
||||
LOG.debug("Total storage for node %s : %d " % (nodeurl, storagecnt))
|
||||
# to convert Bytes in to GB. Divide by 1073741824
|
||||
return str(storagecnt / 1073741824).split(".")[0]
|
||||
|
||||
|
||||
def systems_list(count=None, filters={}):
|
||||
# comment the count value which is set to 2 now..
|
||||
# list of nodes with hardware details needed for flavor creation
|
||||
# count = 2
|
||||
lst_systems = []
|
||||
systemurllist = urls2list("Systems")
|
||||
podmtree = build_hierarchy_tree()
|
||||
#podmtree.writeHTML("0","/tmp/a.html")
|
||||
|
||||
for lnk in systemurllist[:count]:
|
||||
filterPassed = True
|
||||
resp = send_request(lnk)
|
||||
system = resp.json()
|
||||
|
||||
# this below code need to be changed when proper query mechanism
|
||||
# is implemented
|
||||
if any(filters):
|
||||
filterPassed = generic_filter(system, filters)
|
||||
if not filterPassed:
|
||||
continue
|
||||
|
||||
systemid = lnk.split("/")[-1]
|
||||
systemuuid = system['UUID']
|
||||
systemlocation = podmtree.getPath(lnk)
|
||||
cpu = node_cpu_details(lnk)
|
||||
ram = node_ram_details(lnk)
|
||||
nw = node_nw_details(lnk)
|
||||
storage = node_storage_details(lnk)
|
||||
node = {"nodeid": systemid, "cpu": cpu,
|
||||
"ram": ram, "storage": storage,
|
||||
"nw": nw, "location": systemlocation,
|
||||
"uuid": systemuuid}
|
||||
|
||||
# filter based on RAM, CPU, NETWORK..etc
|
||||
if 'ram' in filters:
|
||||
filterPassed = (True
|
||||
if int(ram) >= int(filters['ram'])
|
||||
else False)
|
||||
|
||||
# filter based on RAM, CPU, NETWORK..etc
|
||||
if 'nw' in filters:
|
||||
filterPassed = (True
|
||||
if int(nw) >= int(filters['nw'])
|
||||
else False)
|
||||
|
||||
# filter based on RAM, CPU, NETWORK..etc
|
||||
if 'storage' in filters:
|
||||
filterPassed = (True
|
||||
if int(storage) >= int(filters['storage'])
|
||||
else False)
|
||||
|
||||
if filterPassed:
|
||||
lst_systems.append(node)
|
||||
# LOG.info(str(node))
|
||||
return lst_systems
|
||||
|
||||
|
||||
def get_chassis_list():
|
||||
chassis_lnk_lst = urls2list("Chassis")
|
||||
lst_chassis = []
|
||||
|
||||
for clnk in chassis_lnk_lst:
|
||||
resp = send_request(clnk)
|
||||
data = resp.json()
|
||||
LOG.info(data)
|
||||
if "Links" in data:
|
||||
contains = []
|
||||
containedby = {}
|
||||
computersystems = []
|
||||
linksdata = data["Links"]
|
||||
if "Contains" in linksdata and linksdata["Contains"]:
|
||||
for c in linksdata["Contains"]:
|
||||
contains.append(c['@odata.id'].split("/")[-1])
|
||||
|
||||
if "ContainedBy" in linksdata and linksdata["ContainedBy"]:
|
||||
odata = linksdata["ContainedBy"]['@odata.id']
|
||||
containedby = odata.split("/")[-1]
|
||||
|
||||
if "ComputerSystems" in linksdata and linksdata["ComputerSystems"]:
|
||||
for c in linksdata["ComputerSystems"]:
|
||||
computersystems.append(c['@odata.id'])
|
||||
|
||||
name = data["ChassisType"] + ":" + data["Id"]
|
||||
c = {"name": name,
|
||||
"ChassisType": data["ChassisType"],
|
||||
"ChassisID": data["Id"],
|
||||
"Contains": contains,
|
||||
"ContainedBy": containedby,
|
||||
"ComputerSystems": computersystems}
|
||||
lst_chassis.append(c)
|
||||
return lst_chassis
|
||||
|
||||
|
||||
def get_nodebyid(nodeid):
|
||||
resp = send_request("Nodes/" + nodeid)
|
||||
return resp.json()
|
||||
|
||||
|
||||
def build_hierarchy_tree():
|
||||
# builds the tree sturcture of the PODM data to get the location hierarchy
|
||||
lst_chassis = get_chassis_list()
|
||||
podmtree = tree.Tree()
|
||||
podmtree.add_node("0") # Add root node
|
||||
for d in lst_chassis:
|
||||
podmtree.add_node(d["ChassisID"], d)
|
||||
|
||||
for d in lst_chassis:
|
||||
containedby = d["ContainedBy"] if d["ContainedBy"] else "0"
|
||||
podmtree.add_node(d["ChassisID"], d, containedby)
|
||||
systems = d["ComputerSystems"]
|
||||
for system in systems:
|
||||
sysname = system.split("/")[-2] + ":" + system.split("/")[-1]
|
||||
podmtree.add_node(system, {"name": sysname}, d["ChassisID"])
|
||||
return podmtree
|
||||
|
||||
def compose_node(criteria={}):
|
||||
#node comosition
|
||||
composeurl = "Nodes/Actions/Allocate"
|
||||
reqbody = None if not criteria else criteria
|
||||
headers = {'Content-type': 'application/json'}
|
||||
if not criteria:
|
||||
resp = send_request(composeurl, "POST", headers = headers)
|
||||
else:
|
||||
resp = send_request(composeurl, "POST", json=criteria, headers = headers)
|
||||
LOG.info(resp.headers)
|
||||
LOG.info(resp.text)
|
||||
LOG.info(resp.status_code)
|
||||
composednode = resp.headers['Location']
|
||||
|
||||
return { "node" : composednode }
|
||||
|
||||
|
||||
def delete_composednode(nodeid):
|
||||
#delete composed node
|
||||
deleteurl = "Nodes/" + str(nodeid)
|
||||
resp = send_request(deleteurl, "DELETE")
|
||||
return resp
|
||||
|
||||
def nodes_list(count=None, filters={}):
|
||||
# comment the count value which is set to 2 now..
|
||||
# list of nodes with hardware details needed for flavor creation
|
||||
# count = 2
|
||||
lst_nodes = []
|
||||
nodeurllist = urls2list("Nodes")
|
||||
#podmtree = build_hierarchy_tree()
|
||||
#podmtree.writeHTML("0","/tmp/a.html")
|
||||
|
||||
for lnk in nodeurllist:
|
||||
filterPassed = True
|
||||
resp = send_request(lnk)
|
||||
if resp.status_code != 200:
|
||||
Log.info("Error in fetching Node details " + lnk)
|
||||
else:
|
||||
node = resp.json()
|
||||
|
||||
# this below code need to be changed when proper query mechanism
|
||||
# is implemented
|
||||
if any(filters):
|
||||
filterPassed = generic_filter(node, filters)
|
||||
if not filterPassed:
|
||||
continue
|
||||
|
||||
nodeid = lnk.split("/")[-1]
|
||||
nodeuuid = node['UUID']
|
||||
nodelocation = node['AssetTag']
|
||||
#podmtree.getPath(lnk) commented as location should be computed using
|
||||
#other logic.consult Chester
|
||||
nodesystemurl = node["Links"]["ComputerSystem"]["@odata.id"]
|
||||
cpu = {}
|
||||
ram = 0
|
||||
nw = 0
|
||||
localstorage = node_storage_details(nodesystemurl)
|
||||
if "Processors" in node:
|
||||
cpu = { "count" : node["Processors"]["Count"],
|
||||
"model" : node["Processors"]["Model"]}
|
||||
|
||||
if "Memory" in node:
|
||||
ram = node["Memory"]["TotalSystemMemoryGiB"]
|
||||
|
||||
if "EthernetInterfaces" in node["Links"]:
|
||||
nw = len(node["Links"]["EthernetInterfaces"])
|
||||
|
||||
storage = 0
|
||||
bmcip = "127.0.0.1" #system['Oem']['Dell_G5MC']['BmcIp']
|
||||
bmcmac = "00:00:00:00:00" #system['Oem']['Dell_G5MC']['BmcMac']
|
||||
node = {"nodeid": nodeid, "cpu": cpu,
|
||||
"ram": ram, "storage": localstorage,
|
||||
"nw": nw, "location": nodelocation,
|
||||
"uuid": nodeuuid, "bmcip": bmcip, "bmcmac": bmcmac}
|
||||
if filterPassed:
|
||||
lst_nodes.append(node)
|
||||
# LOG.info(str(node))
|
||||
return lst_nodes
|
33
valence/common/redfish/config.py
Normal file
33
valence/common/redfish/config.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
# Configurations
|
||||
podm_opts = [
|
||||
cfg.StrOpt('url',
|
||||
default='http://localhost:80',
|
||||
help=("The complete url string of PODM")),
|
||||
cfg.StrOpt('user',
|
||||
default='admin',
|
||||
help=("User for the PODM")),
|
||||
cfg.StrOpt('password',
|
||||
default='admin',
|
||||
help=("Passoword for PODM"))]
|
||||
|
||||
podm_conf_group = cfg.OptGroup(name='podm', title='RSC PODM options')
|
||||
cfg.CONF.register_group(podm_conf_group)
|
||||
cfg.CONF.register_opts(podm_opts, group=podm_conf_group)
|
122
valence/common/redfish/tree.py
Normal file
122
valence/common/redfish/tree.py
Normal file
@ -0,0 +1,122 @@
|
||||
(_ROOT, _DEPTH, _BREADTH) = range(3)
|
||||
|
||||
|
||||
class Tree(object):
|
||||
|
||||
def __init__(self):
|
||||
self.__nodes = {}
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
return self.__nodes
|
||||
|
||||
def add_node(self, identifier, data={}, parent=None):
|
||||
if identifier in self.nodes:
|
||||
node = self[identifier]
|
||||
else:
|
||||
node = TreeNode(identifier,data)
|
||||
self[identifier] = node
|
||||
|
||||
if parent is not None:
|
||||
self[parent].add_child(identifier)
|
||||
self[identifier].set_parent(parent)
|
||||
return node
|
||||
|
||||
def display(self, identifier, depth=_ROOT):
|
||||
children = self[identifier].children
|
||||
# data = self[identifier].data
|
||||
if depth == _ROOT:
|
||||
print("{0}".format(identifier))
|
||||
else:
|
||||
print("\t" * depth, "{0}".format(identifier))
|
||||
|
||||
depth += 1
|
||||
for child in children:
|
||||
self.display(child, depth) # recursive call
|
||||
|
||||
def processHTML(self, fileref, identifier, depth=_ROOT):
|
||||
# generate the tree structure in html.
|
||||
# the enclosing html should be included in the calling function
|
||||
|
||||
fileref.write("<ul>")
|
||||
children = self[identifier].children
|
||||
if self[identifier].data:
|
||||
name = self[identifier].data['name']
|
||||
else:
|
||||
name = identifier
|
||||
|
||||
htmlstr = "<li>" + name + "[" + identifier + "]</li>"
|
||||
|
||||
fileref.write(htmlstr)
|
||||
depth += 1
|
||||
for child in children:
|
||||
self.processHTML(fileref, child, depth) # recursive call
|
||||
fileref.write("</ul>")
|
||||
|
||||
def writeHTML(self, rootnodeid, filename="chassisTree.html"):
|
||||
htmlfile = open(filename, 'w+')
|
||||
htmlfile.write("<html><body><h1>Tree</h1>")
|
||||
self.processHTML(htmlfile, rootnodeid)
|
||||
htmlfile.write("</body></html>")
|
||||
htmlfile.close()
|
||||
|
||||
def traverse(self, identifier, mode=_DEPTH):
|
||||
# Python generator. Loosly based on an algorithm from
|
||||
# 'Essential LISP' by John R. Anderson, Albert T. Corbett,
|
||||
# and Brian J. Reiser, page 239-241
|
||||
yield identifier
|
||||
queue = self[identifier].children
|
||||
while queue:
|
||||
yield queue[0]
|
||||
expansion = self[queue[0]].children
|
||||
if mode == _DEPTH:
|
||||
queue = expansion + queue[1:] # depth-first
|
||||
elif mode == _BREADTH:
|
||||
queue = queue[1:] + expansion # width-first
|
||||
|
||||
def getPath(self, identifier):
|
||||
if self[identifier].parent is not None:
|
||||
parentpath = self.getPath(self[identifier].parent)
|
||||
return self[identifier].data["name"] + "_" + parentpath
|
||||
else:
|
||||
if self[identifier].data:
|
||||
return self[identifier].data['name']
|
||||
else:
|
||||
return ""
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__nodes[key]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
self.__nodes[key] = item
|
||||
|
||||
|
||||
# Class represents Tree Node
|
||||
class TreeNode(object):
|
||||
def __init__(self, identifier, data={}):
|
||||
self.__identifier = identifier
|
||||
self.__children = []
|
||||
self.__parent = None
|
||||
self.__data = data
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self.__identifier
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return self.__children
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.__parent
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self.__data
|
||||
|
||||
def add_child(self, identifier):
|
||||
self.__children.append(identifier)
|
||||
|
||||
def set_parent(self, identifier):
|
||||
self.__parent = identifier
|
139
valence/common/rpc.py
Normal file
139
valence/common/rpc.py
Normal file
@ -0,0 +1,139 @@
|
||||
# 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.
|
||||
|
||||
|
||||
# import oslo_messaging as messaging
|
||||
# from oslo_serialization import jsonutils
|
||||
# from valence.common import valencecontext
|
||||
from oslo_config import cfg
|
||||
import oslo_messaging as messaging
|
||||
from oslo_serialization import jsonutils
|
||||
from valence.common import context as valence_ctx
|
||||
import valence.common.exceptions
|
||||
|
||||
|
||||
__all__ = [
|
||||
'init',
|
||||
'cleanup',
|
||||
'set_defaults',
|
||||
'add_extra_exmods',
|
||||
'clear_extra_exmods',
|
||||
'get_allowed_exmods',
|
||||
'RequestContextSerializer',
|
||||
'get_client',
|
||||
'get_server',
|
||||
'get_notifier',
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
TRANSPORT = None
|
||||
NOTIFIER = None
|
||||
|
||||
ALLOWED_EXMODS = [
|
||||
valence.common.exceptions.__name__,
|
||||
]
|
||||
EXTRA_EXMODS = []
|
||||
|
||||
|
||||
def init(conf):
|
||||
global TRANSPORT, NOTIFIER
|
||||
exmods = get_allowed_exmods()
|
||||
TRANSPORT = messaging.get_transport(conf,
|
||||
allowed_remote_exmods=exmods)
|
||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
||||
NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer)
|
||||
|
||||
|
||||
def cleanup():
|
||||
global TRANSPORT, NOTIFIER
|
||||
assert TRANSPORT is not None
|
||||
assert NOTIFIER is not None
|
||||
TRANSPORT.cleanup()
|
||||
TRANSPORT = NOTIFIER = None
|
||||
|
||||
|
||||
def set_defaults(control_exchange):
|
||||
messaging.set_transport_defaults(control_exchange)
|
||||
|
||||
|
||||
def add_extra_exmods(*args):
|
||||
EXTRA_EXMODS.extend(args)
|
||||
|
||||
|
||||
def clear_extra_exmods():
|
||||
del EXTRA_EXMODS[:]
|
||||
|
||||
|
||||
def get_allowed_exmods():
|
||||
return ALLOWED_EXMODS + EXTRA_EXMODS
|
||||
|
||||
|
||||
class JsonPayloadSerializer(messaging.NoOpSerializer):
|
||||
@staticmethod
|
||||
def serialize_entity(context, entity):
|
||||
return jsonutils.to_primitive(entity, convert_instances=True)
|
||||
|
||||
|
||||
class RequestContextSerializer(messaging.Serializer):
|
||||
|
||||
def __init__(self, base):
|
||||
self._base = base
|
||||
|
||||
def serialize_entity(self, context, entity):
|
||||
if not self._base:
|
||||
return entity
|
||||
return self._base.serialize_entity(context, entity)
|
||||
|
||||
def deserialize_entity(self, context, entity):
|
||||
if not self._base:
|
||||
return entity
|
||||
return self._base.deserialize_entity(context, entity)
|
||||
|
||||
def serialize_context(self, context):
|
||||
if isinstance(context, dict):
|
||||
return context
|
||||
else:
|
||||
return context.to_dict()
|
||||
|
||||
def deserialize_context(self, context):
|
||||
# return valence.common.context.Context.from_dict(context)
|
||||
return valence_ctx.Context.from_dict(context)
|
||||
|
||||
|
||||
def get_transport_url(url_str=None):
|
||||
return messaging.TransportURL.parse(CONF, url_str)
|
||||
|
||||
|
||||
def get_client(target, version_cap=None, serializer=None):
|
||||
assert TRANSPORT is not None
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
return messaging.RPCClient(TRANSPORT,
|
||||
target,
|
||||
version_cap=version_cap,
|
||||
serializer=serializer)
|
||||
|
||||
|
||||
def get_server(target, endpoints, serializer=None):
|
||||
assert TRANSPORT is not None
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
return messaging.get_rpc_server(TRANSPORT,
|
||||
target,
|
||||
endpoints,
|
||||
executor='eventlet',
|
||||
serializer=serializer)
|
||||
|
||||
|
||||
def get_notifier(service, host=None, publisher_id=None):
|
||||
assert NOTIFIER is not None
|
||||
if not publisher_id:
|
||||
publisher_id = "%s.%s" % (service, host or CONF.host)
|
||||
return NOTIFIER.prepare(publisher_id=publisher_id)
|
89
valence/common/rpc_service.py
Normal file
89
valence/common/rpc_service.py
Normal file
@ -0,0 +1,89 @@
|
||||
# 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.
|
||||
|
||||
"""Common RPC service and API tools for Valence."""
|
||||
|
||||
import eventlet
|
||||
from oslo_config import cfg
|
||||
import oslo_messaging as messaging
|
||||
from oslo_service import service
|
||||
|
||||
from valence.common import rpc
|
||||
from valence.objects import base as objects_base
|
||||
|
||||
eventlet.monkey_patch()
|
||||
|
||||
periodic_opts = [
|
||||
cfg.IntOpt('periodic_interval_max',
|
||||
default=60,
|
||||
help='Max interval size between periodic tasks execution in '
|
||||
'seconds.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(periodic_opts)
|
||||
|
||||
|
||||
class Service(service.Service):
|
||||
|
||||
def __init__(self, topic, server, handlers, binary):
|
||||
super(Service, self).__init__()
|
||||
serializer = rpc.RequestContextSerializer(
|
||||
objects_base.ValenceObjectSerializer())
|
||||
transport = messaging.get_transport(cfg.CONF)
|
||||
# TODO(asalkeld) add support for version='x.y'
|
||||
target = messaging.Target(topic=topic, server=server)
|
||||
self._server = messaging.get_rpc_server(transport, target, handlers,
|
||||
serializer=serializer)
|
||||
self.binary = binary
|
||||
|
||||
def start(self):
|
||||
# servicegroup.setup(CONF, self.binary, self.tg)
|
||||
self._server.start()
|
||||
|
||||
def stop(self):
|
||||
if self._server:
|
||||
self._server.stop()
|
||||
self._server.wait()
|
||||
super(Service, self).stop()
|
||||
|
||||
@classmethod
|
||||
def create(cls, topic, server, handlers, binary):
|
||||
service_obj = cls(topic, server, handlers, binary)
|
||||
return service_obj
|
||||
|
||||
|
||||
class API(object):
|
||||
def __init__(self, transport=None, context=None, topic=None, server=None,
|
||||
timeout=None):
|
||||
serializer = rpc.RequestContextSerializer(
|
||||
objects_base.ValenceObjectSerializer())
|
||||
if transport is None:
|
||||
exmods = rpc.get_allowed_exmods()
|
||||
transport = messaging.get_transport(cfg.CONF,
|
||||
allowed_remote_exmods=exmods)
|
||||
self._context = context
|
||||
if topic is None:
|
||||
topic = ''
|
||||
target = messaging.Target(topic=topic, server=server)
|
||||
self._client = messaging.RPCClient(transport, target,
|
||||
serializer=serializer,
|
||||
timeout=timeout)
|
||||
|
||||
def _call(self, method, *args, **kwargs):
|
||||
return self._client.call(self._context, method, *args, **kwargs)
|
||||
|
||||
def _cast(self, method, *args, **kwargs):
|
||||
self._client.cast(self._context, method, *args, **kwargs)
|
||||
|
||||
def echo(self, message):
|
||||
self._cast('echo', message=message)
|
0
valence/controller/__init__.py
Normal file
0
valence/controller/__init__.py
Normal file
67
valence/controller/api.py
Normal file
67
valence/controller/api.py
Normal file
@ -0,0 +1,67 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""controller API for interfacing with Other modules"""
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from valence.common import rpc_service
|
||||
|
||||
|
||||
# The Backend API class serves as a AMQP client for communicating
|
||||
# on a topic exchange specific to the controllers. This allows the ReST
|
||||
# API to trigger operations on the controllers
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class API(rpc_service.API):
|
||||
def __init__(self, transport=None, context=None, topic=None):
|
||||
if topic is None:
|
||||
cfg.CONF.import_opt('topic', 'valence.controller.config',
|
||||
group='controller')
|
||||
super(API, self).__init__(transport, context,
|
||||
topic=cfg.CONF.controller.topic)
|
||||
|
||||
# Flavor Operations
|
||||
|
||||
def flavor_options(self):
|
||||
return self._call('flavor_options')
|
||||
|
||||
def flavor_generate(self, criteria):
|
||||
return self._call('flavor_generate', criteria=criteria)
|
||||
|
||||
# Node(s) Operations
|
||||
def list_nodes(self, filters):
|
||||
return self._call('list_nodes', filters=filters)
|
||||
|
||||
def get_nodebyid(self, nodeid):
|
||||
return self._call('get_nodebyid', nodeid=nodeid)
|
||||
|
||||
def delete_composednode(self, nodeid):
|
||||
return self._call('delete_composednode', nodeid=nodeid)
|
||||
|
||||
def update_node(self, nodeid):
|
||||
return self._call('update_node')
|
||||
|
||||
def compose_nodes(self, criteria):
|
||||
return self._call('compose_nodes', criteria=criteria)
|
||||
|
||||
def list_node_storages(self, data):
|
||||
return self._call('list_node_storages')
|
||||
|
||||
def map_node_storage(self, data):
|
||||
return self._call('map_node_storage')
|
||||
|
||||
def delete_node_storage(self, data):
|
||||
return self._call('delete_node_storage')
|
65
valence/controller/config.py
Normal file
65
valence/controller/config.py
Normal file
@ -0,0 +1,65 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Config options for Valence controller Service"""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import sys
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONTROLLER_OPTS = [
|
||||
cfg.StrOpt('topic',
|
||||
default='valence-controller',
|
||||
help='The queue to add controller tasks to.')
|
||||
]
|
||||
|
||||
OS_INTERFACE_OPTS = [
|
||||
cfg.StrOpt('os_admin_url',
|
||||
help='Admin URL of Openstack'),
|
||||
cfg.StrOpt('os_tenant',
|
||||
default='admin',
|
||||
help='Tenant for Openstack'),
|
||||
cfg.StrOpt('os_user',
|
||||
default='admin',
|
||||
help='User for openstack'),
|
||||
cfg.StrOpt('os_password',
|
||||
default='addmin',
|
||||
help='Password for openstack')
|
||||
]
|
||||
|
||||
controller_conf_group = cfg.OptGroup(name='controller',
|
||||
title='Valence controller options')
|
||||
cfg.CONF.register_group(controller_conf_group)
|
||||
cfg.CONF.register_opts(CONTROLLER_OPTS, group=controller_conf_group)
|
||||
|
||||
os_conf_group = cfg.OptGroup(name='undercloud',
|
||||
title='Valence Openstack interface options')
|
||||
cfg.CONF.register_group(os_conf_group)
|
||||
cfg.CONF.register_opts(OS_INTERFACE_OPTS, group=os_conf_group)
|
||||
|
||||
|
||||
def init(args, **kwargs):
|
||||
# Register the configuration options
|
||||
logging.register_options(cfg.CONF)
|
||||
cfg.CONF(args=args, project='valence', **kwargs)
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Sets up the logging options for a log with supplied name."""
|
||||
domain = "valence"
|
||||
logging.setup(cfg.CONF, domain)
|
||||
LOG.info("Logging enabled!")
|
||||
LOG.debug("command line: %s", " ".join(sys.argv))
|
0
valence/controller/handlers/__init__.py
Normal file
0
valence/controller/handlers/__init__.py
Normal file
37
valence/controller/handlers/flavor_controller.py
Normal file
37
valence/controller/handlers/flavor_controller.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from valence.flavor import flavor
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Handler(object):
|
||||
"""Valence Flavor RPC handler.
|
||||
|
||||
These are the backend operations. They are executed by the backend ervice.
|
||||
API calls via AMQP (within the ReST API) trigger the handlers to be called.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(Handler, self).__init__()
|
||||
|
||||
def flavor_options(self, context):
|
||||
return flavor.get_available_criteria()
|
||||
|
||||
def flavor_generate(self, context, criteria):
|
||||
LOG.debug("Getting flavor options")
|
||||
return flavor.create_flavors(criteria)
|
64
valence/controller/handlers/node_controller.py
Normal file
64
valence/controller/handlers/node_controller.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import json
|
||||
from oslo_log import log as logging
|
||||
from valence.common import osinterface as osapi
|
||||
from valence.common.redfish import api as rfsapi
|
||||
import requests
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Handler(object):
|
||||
"""Valence Node RPC handler.
|
||||
|
||||
These are the backend operations. They are executed by the backend ervice.
|
||||
API calls via AMQP (within the ReST API) trigger the handlers to be called.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(Handler, self).__init__()
|
||||
|
||||
def list_nodes(self, context, filters):
|
||||
LOG.info(str(filters))
|
||||
return rfsapi.nodes_list(None, filters)
|
||||
|
||||
def get_nodebyid(self, context, nodeid):
|
||||
return rfsapi.get_nodebyid(nodeid)
|
||||
|
||||
def delete_composednode(self, context, nodeid):
|
||||
return rfsapi.delete_composednode(nodeid)
|
||||
|
||||
def update_node(self, context, nodeid):
|
||||
return {"node": "Update node attributes"}
|
||||
|
||||
def compose_nodes(self, context, criteria):
|
||||
"""Chassis details could also be fetched and inserted"""
|
||||
|
||||
# no of nodes to compose
|
||||
nodes_to_compose = int(criteria["nodes"]) if "nodes" in criteria else 1
|
||||
node_criteria = criteria["filter"] if "filter" in criteria else {}
|
||||
#no of node is not currently implemented
|
||||
return rfsapi.compose_node(node_criteria)
|
||||
|
||||
def list_node_storages(self, context, data):
|
||||
return {"node": "List the storages attached to the node"}
|
||||
|
||||
def map_node_storage(self, context, data):
|
||||
return {"node": "Map storages to a node"}
|
||||
|
||||
def delete_node_storage(self, context, data):
|
||||
return {"node": "Deleted storages mapped to a node"}
|
0
valence/flavor/__init__.py
Normal file
0
valence/flavor/__init__.py
Normal file
54
valence/flavor/flavor.py
Normal file
54
valence/flavor/flavor.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from importlib import import_module
|
||||
# from valence.flavor.plugins import *
|
||||
import os
|
||||
from oslo_log import log as logging
|
||||
from valence.common.redfish import api as rfs
|
||||
|
||||
FLAVOR_PLUGIN_PATH = os.path.dirname(os.path.abspath(__file__)) + '/plugins'
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def get_available_criteria():
|
||||
pluginfiles = [f.split('.')[0]
|
||||
for f in os.listdir(FLAVOR_PLUGIN_PATH)
|
||||
if os.path.isfile(os.path.join(FLAVOR_PLUGIN_PATH, f))
|
||||
and not f.startswith('__') and f.endswith('.py')]
|
||||
resp = []
|
||||
for p in pluginfiles:
|
||||
module = import_module("valence.flavor.plugins." + p)
|
||||
myclass = getattr(module, p + 'Generator')
|
||||
inst = myclass([])
|
||||
resp.append({'name': p, 'description': inst.description()})
|
||||
return {'criteria': resp}
|
||||
|
||||
|
||||
def create_flavors(criteria):
|
||||
"""criteria : comma seperated generator names
|
||||
|
||||
This should be same as thier file name)
|
||||
|
||||
"""
|
||||
respjson = []
|
||||
lst_nodes = rfs.nodes_list()
|
||||
for g in criteria.split(","):
|
||||
if g:
|
||||
logger.info("Calling generator : %s ." % g)
|
||||
module = __import__("valence.flavor.plugins." + g, fromlist=["*"])
|
||||
classobj = getattr(module, g + "Generator")
|
||||
inst = classobj(lst_nodes)
|
||||
respjson.append(inst.generate())
|
||||
return respjson
|
37
valence/flavor/generatorbase.py
Normal file
37
valence/flavor/generatorbase.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
|
||||
class generatorbase(object):
|
||||
def __init__(self, nodes):
|
||||
self.nodes = nodes
|
||||
self.prepend_name = 'irsd-'
|
||||
|
||||
def description(self):
|
||||
return "Description of plugins"
|
||||
|
||||
def _flavor_template(self, name, ram, cpus, disk, extraspecs):
|
||||
return json.dumps([{"flavor":
|
||||
{"name": name,
|
||||
"ram": int(ram),
|
||||
"vcpus": int(cpus),
|
||||
"disk": int(disk),
|
||||
"id": str(uuid.uuid4())}},
|
||||
{"extra_specs": extraspecs}])
|
||||
|
||||
def generate(self):
|
||||
raise NotImplementedError()
|
5
valence/flavor/plugins/__init__.py
Normal file
5
valence/flavor/plugins/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""from os.path import dirname, basename, isfile
|
||||
import glob
|
||||
modules = glob.glob(dirname(__file__)+"/*.py")
|
||||
__all__ = [ basename(f)[:-3] for f in modules if isfile(f)]
|
||||
"""
|
46
valence/flavor/plugins/assettag.py
Normal file
46
valence/flavor/plugins/assettag.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import re
|
||||
from oslo_log import log as logging
|
||||
from valence.flavor.generatorbase import generatorbase
|
||||
|
||||
LOG = logging.getLogger()
|
||||
|
||||
class assettagGenerator(generatorbase):
|
||||
def __init__(self, nodes):
|
||||
generatorbase.__init__(self, nodes)
|
||||
|
||||
def description(self):
|
||||
return "Demo only: Generates location based on assettag"
|
||||
|
||||
def generate(self):
|
||||
LOG.info("Default Generator")
|
||||
for node in self.nodes:
|
||||
LOG.info("Node ID " + node['nodeid'])
|
||||
location = node['location']
|
||||
location = location.split('Sled')[0]
|
||||
#Systems:Rack1-Block1-Sled2-Node1_Sled:Rack1-Block1-Sled2_Enclosure:Rack1-Block1_Rack:Rack1_
|
||||
location_lst = re.split("(\d+)", location)
|
||||
LOG.info(str(location_lst))
|
||||
location_lst = list(filter(None, location_lst))
|
||||
LOG.info(str(location_lst))
|
||||
extraspecs = {location_lst[i]: location_lst[i+1] for i in range(0,len(location_lst),2)}
|
||||
name = self.prepend_name + location
|
||||
return {
|
||||
self._flavor_template("L_" + name, node['ram'] , node['cpu']["count"], node['storage'], extraspecs),
|
||||
self._flavor_template("M_" + name, int(node['ram'])/2 , int(node['cpu']["count"])/2 , int(node['storage'])/2, extraspecs),
|
||||
self._flavor_template("S_" + name, int(node['ram'])/4 , int(node['cpu']["count"])/4 , int(node['storage'])/4, extraspecs)
|
||||
}
|
43
valence/flavor/plugins/default.py
Normal file
43
valence/flavor/plugins/default.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import re
|
||||
from oslo_log import log as logging
|
||||
from valence.flavor.generatorbase import generatorbase
|
||||
|
||||
LOG = logging.getLogger()
|
||||
|
||||
class defaultGenerator(generatorbase):
|
||||
def __init__(self, nodes):
|
||||
generatorbase.__init__(self, nodes)
|
||||
|
||||
def description(self):
|
||||
return "Generates 3 flavors(Tiny, Medium, Large) for each node considering all cpu cores, ram and storage"
|
||||
|
||||
def generate(self):
|
||||
LOG.info("Default Generator")
|
||||
for node in self.nodes:
|
||||
LOG.info("Node ID " + node['nodeid'])
|
||||
location = node['location']
|
||||
#Systems:Rack1-Block1-Sled2-Node1_Sled:Rack1-Block1-Sled2_Enclosure:Rack1-Block1_Rack:Rack1_
|
||||
location_lst = location.split("_");
|
||||
location_lst = list(filter(None, location_lst))
|
||||
extraspecs = { l[0] : l[1] for l in (l.split(":") for l in location_lst) }
|
||||
name = self.prepend_name + location
|
||||
return {
|
||||
self._flavor_template("L_" + name, node['ram'] , node['cpu']["count"], node['storage'], extraspecs),
|
||||
self._flavor_template("M_" + name, int(node['ram'])/2 , int(node['cpu']["count"])/2 , int(node['storage'])/2, extraspecs),
|
||||
self._flavor_template("S_" + name, int(node['ram'])/4 , int(node['cpu']["count"])/4 , int(node['storage'])/4, extraspecs)
|
||||
}
|
27
valence/flavor/plugins/example.py
Normal file
27
valence/flavor/plugins/example.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from valence.flavor.generatorbase import generatorbase
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class exampleGenerator(generatorbase):
|
||||
def __init__(self, nodes):
|
||||
generatorbase.__init__(self, nodes)
|
||||
|
||||
def generate(self):
|
||||
logger.info("Example Flavor Generate")
|
||||
return {"Error": "Example Flavor Generator- Not Yet Implemented"}
|
0
valence/objects/__init__.py
Normal file
0
valence/objects/__init__.py
Normal file
63
valence/objects/base.py
Normal file
63
valence/objects/base.py
Normal file
@ -0,0 +1,63 @@
|
||||
# Copyright (c) 2016 Intel, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Valence common internal object model"""
|
||||
|
||||
from oslo_versionedobjects import base as ovoo_base
|
||||
from oslo_versionedobjects import fields as ovoo_fields
|
||||
|
||||
|
||||
remotable_classmethod = ovoo_base.remotable_classmethod
|
||||
remotable = ovoo_base.remotable
|
||||
|
||||
|
||||
class ValenceObjectRegistry(ovoo_base.VersionedObjectRegistry):
|
||||
pass
|
||||
|
||||
|
||||
class ValenceObject(ovoo_base.VersionedObject):
|
||||
"""Base class and object factory.
|
||||
|
||||
This forms the base of all objects that can be remoted or instantiated
|
||||
via RPC. Simply defining a class that inherits from this base class
|
||||
will make it remotely instantiatable. Objects should implement the
|
||||
necessary "get" classmethod routines as well as "save" object methods
|
||||
as appropriate.
|
||||
"""
|
||||
OBJ_PROJECT_NAMESPACE = 'Valence'
|
||||
|
||||
def as_dict(self):
|
||||
return {k: getattr(self, k)
|
||||
for k in self.fields
|
||||
if self.obj_attr_is_set(k)}
|
||||
|
||||
|
||||
class ValenceObjectDictCompat(ovoo_base.VersionedObjectDictCompat):
|
||||
pass
|
||||
|
||||
|
||||
class ValencePersistentObject(object):
|
||||
"""Mixin class for Persistent objects.
|
||||
|
||||
This adds the fields that we use in common for all persistent objects.
|
||||
"""
|
||||
fields = {
|
||||
'created_at': ovoo_fields.DateTimeField(nullable=True),
|
||||
'updated_at': ovoo_fields.DateTimeField(nullable=True),
|
||||
}
|
||||
|
||||
|
||||
class ValenceObjectSerializer(ovoo_base.VersionedObjectSerializer):
|
||||
# Base class to use for object hydration
|
||||
OBJ_BASE_CLASS = ValenceObject
|
24
valence/tests/__init__.py
Normal file
24
valence/tests/__init__.py
Normal file
@ -0,0 +1,24 @@
|
||||
import os
|
||||
from pecan import set_config
|
||||
from pecan.testing import load_test_app
|
||||
from unittest import TestCase
|
||||
|
||||
__all__ = ['FunctionalTest']
|
||||
|
||||
|
||||
class FunctionalTest(TestCase):
|
||||
"""Functional Test Class
|
||||
|
||||
Used for functional tests where you need to test your
|
||||
literal application and its integration with the framework.
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.app = load_test_app(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'config.py'
|
||||
))
|
||||
|
||||
def tearDown(self):
|
||||
set_config({}, overwrite=True)
|
37
valence/tests/config.py
Normal file
37
valence/tests/config.py
Normal file
@ -0,0 +1,37 @@
|
||||
# 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.
|
||||
|
||||
# Server Specific Configurations
|
||||
server = {
|
||||
'port': '8080',
|
||||
'host': '0.0.0.0'
|
||||
}
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root': 'valence.controllers.root.RootController',
|
||||
'modules': ['valence'],
|
||||
'static_root': '%(confdir)s/../../public',
|
||||
'template_path': '%(confdir)s/../templates',
|
||||
'debug': True,
|
||||
'errors': {
|
||||
'404': '/error/404',
|
||||
'__force_dict__': True
|
||||
}
|
||||
}
|
||||
|
||||
# Custom Configurations must be in Python dictionary format::
|
||||
#
|
||||
# foo = {'bar':'baz'}
|
||||
#
|
||||
# All configurations are accessible at::
|
||||
# pecan.conf
|
22
valence/tests/test_functional.py
Normal file
22
valence/tests/test_functional.py
Normal file
@ -0,0 +1,22 @@
|
||||
from valence.tests import FunctionalTest
|
||||
# from unittest import TestCase
|
||||
# from webtest import TestApp
|
||||
|
||||
|
||||
class TestRootController(FunctionalTest):
|
||||
|
||||
def test_get(self):
|
||||
response = self.app.get('/')
|
||||
assert response.status_int == 200
|
||||
|
||||
def test_search(self):
|
||||
response = self.app.post('/', params={'q': 'RestController'})
|
||||
assert response.status_int == 302
|
||||
assert response.headers['Location'] == (
|
||||
'http://pecan.readthedocs.org/en/latest/search.html'
|
||||
'?q=RestController'
|
||||
)
|
||||
|
||||
def test_get_not_found(self):
|
||||
response = self.app.get('/a/bogus/url', expect_errors=True)
|
||||
assert response.status_int == 404
|
7
valence/tests/test_units.py
Normal file
7
valence/tests/test_units.py
Normal file
@ -0,0 +1,7 @@
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestUnits(TestCase):
|
||||
|
||||
def test_units(self):
|
||||
assert 5 * 5 == 25
|
44
valence/ui/README.md
Normal file
44
valence/ui/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
Rack Scale Design (RSD) Web UI
|
||||
==============================
|
||||
|
||||
The `ui` folder contains HTML, JavaScript and CSS code for a Web UI that can be used to explore Rack Scale Design (RSD) artifacts and compose/disassemble nodes.
|
||||
|
||||
##Pre-reqs
|
||||
1. Install Node and NPM using the OS-specific installer on <https://nodejs.org/en/download/>
|
||||
2. Update npm to the latest verions
|
||||
```
|
||||
sudo npm install npm -g
|
||||
```
|
||||
3. Follow the instructions in the docs directory for setting up the apache ui-proxy.
|
||||
|
||||
##Install
|
||||
1. `cd` to the `ui` directory and run:
|
||||
```
|
||||
npm install
|
||||
```
|
||||
* This will install all packages listed in `package.json` file.
|
||||
* If you are adding a new package dependency, make sure to save it to the `package.json` file. You can install the package and update `package.json` in a single command: `npm install --save new-package@6.2.5`
|
||||
* This installs the webpack dev server which can be used for serving the Web UI during development.
|
||||
|
||||
##Run
|
||||
1. Build
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
2. Start webpack-dev-server in watch mode on the `src` dir:
|
||||
```
|
||||
npm run devserver
|
||||
```
|
||||
* The `devserver` command is defined in `package.json`. It launches the `webpack-dev-server` program in `hot` mode and watches the `src` directory. If you make any changes to any file in the `src` dir, `webpack-dev-server` compiles everything to a temp location and reloads the display page (`index.html`).
|
||||
|
||||
3. Open browser and goto <http://localhost:8080/> to view the UI
|
||||
|
||||
##Develop
|
||||
1. The `src\index.html` is the root HTML page for the Web UI. It has a `div` element called `app` which is where the dynamic UI contents get inserted. The file `src/js/main.js` does this insertion using:
|
||||
```
|
||||
ReactDOM.render(<Layout/>, document.getElementById('app'));
|
||||
```
|
||||
The root of the app content is provided by the React component `src/js/components/Layout.js`. It wraps others components Pods.js, Racks.js, etc which encapsulate the state and rendering details of Pods, Rack, etc respectively.
|
||||
2. The file `webpack.config.js` contains loaders that transpile React components to plain JavaScript that any browser can understand. The command `webpack` (`package.json` contains `dev-build` and `build` commands which can be used instead via `npm run <command>`) kicks off this transpilation process.
|
||||
3. Modify appropriate files and use the devserver detailed above to test your changes.
|
||||
|
61
valence/ui/package.json
Normal file
61
valence/ui/package.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "rsd-webui",
|
||||
"version": "0.1.0",
|
||||
"description": "Web UI to explore Rack Scale Design (RSD) artifacts and compose/disassemble nodes.",
|
||||
"main": "src/main.js",
|
||||
"keywords": [
|
||||
"rsd",
|
||||
"UI",
|
||||
"compose",
|
||||
"disassemble"
|
||||
],
|
||||
"dependencies": {
|
||||
"bootstrap-sass": "^3.3.6",
|
||||
"jquery": "^3.1.0",
|
||||
"react": "^0.14.6",
|
||||
"react-dom": "^0.14.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.4.5",
|
||||
"babel-loader": "^6.2.0",
|
||||
"babel-plugin-add-module-exports": "^0.1.2",
|
||||
"babel-plugin-react-html-attrs": "^2.0.0",
|
||||
"babel-plugin-transform-class-properties": "^6.3.13",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-stage-0": "^6.3.13",
|
||||
"bootstrap-loader": "^1.1.0",
|
||||
"css-loader": "^0.23.1",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"imports-loader": "^0.6.5",
|
||||
"node-sass": "^3.8.0",
|
||||
"resolve-url-loader": "^1.6.0",
|
||||
"sass-loader": "^4.0.0",
|
||||
"style-loader": "^0.13.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.1",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
},
|
||||
"scripts": {
|
||||
"devserver": "NODE_ENV=development ./node_modules/.bin/webpack-dev-server --progress --colors --content-base src --inline --hot --host 0.0.0.0",
|
||||
"dev-build": "NODE_ENV=development webpack --progress --colors",
|
||||
"build": "NODE_ENV=production webpack --progress --colors",
|
||||
"packages": "npm list --depth=0",
|
||||
"package:purge": "rm -rf node_modules",
|
||||
"package:reinstall": "npm run package:purge && npm install",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Deepti Ramakrishna",
|
||||
"email": "deepti.ramakrishna@intel.com"
|
||||
},
|
||||
{
|
||||
"name": "Lin Yang",
|
||||
"email": "lin.a.yang@intel.com"
|
||||
}
|
||||
],
|
||||
"license": "Apache-2.0"
|
||||
}
|
163
valence/ui/src/customized.css
Normal file
163
valence/ui/src/customized.css
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Base structure
|
||||
*/
|
||||
|
||||
/* Move down content because we have a fixed navbar that is 50px tall */
|
||||
body {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Global add-ons
|
||||
*/
|
||||
|
||||
.sub-header {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
/*
|
||||
* Top navigation
|
||||
* Hide default border to remove 1px line.
|
||||
*/
|
||||
.navbar-fixed-top {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dashboard
|
||||
*/
|
||||
.dashboard {
|
||||
/* padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
*/
|
||||
border-radius: 4px;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.dashboard .row{
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
hr.separator {
|
||||
background-color: #c0c0c0;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.detail-button {
|
||||
background-color: #428bca;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 6px 18px;
|
||||
margin-right: 6px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.compose-button {
|
||||
background-color: #428bca;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 24px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.details {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
.sidebar {
|
||||
position: relative;
|
||||
background-color: #eeeeee;
|
||||
border-radius: 4px;
|
||||
|
||||
/* top: 0px;*/
|
||||
/* bottom: 20;*/
|
||||
/* left: 0;*/
|
||||
/* z-index: 1000;*/
|
||||
/* display: block;*/
|
||||
/* padding: 20px;*/
|
||||
/* overflow-x: hidden;*/
|
||||
/* overflow-y: auto;*/
|
||||
/* background-color: #f5f5f5;*/
|
||||
/* border-right: 1px solid #eee;*/
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Sidebar navigation */
|
||||
.nav-sidebar {
|
||||
/* margin-right: -21px;
|
||||
margin-bottom: 20px;*/
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
}
|
||||
.nav-sidebar > li > a {
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.nav-sidebar > .active > a,
|
||||
.nav-sidebar > .active > a:hover,
|
||||
.nav-sidebar > .active > a:focus {
|
||||
color: #fff;
|
||||
background-color: #428bca;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Main content
|
||||
*/
|
||||
|
||||
.main {
|
||||
padding: 20px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.main {
|
||||
padding-right: 40px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
}
|
||||
.main .page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Placeholder dashboard ideas
|
||||
*/
|
||||
|
||||
.placeholders {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.placeholders h4 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.placeholder {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.placeholder img {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
16
valence/ui/src/index.html
Normal file
16
valence/ui/src/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Rack Scale Design</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<link href='./customized.css' rel='stylesheet' />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="bundle.min.js"></script>
|
||||
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
144
valence/ui/src/js/components/Layout.js
Normal file
144
valence/ui/src/js/components/Layout.js
Normal file
@ -0,0 +1,144 @@
|
||||
import React from "react";
|
||||
import ComposeDisplay from "./home/ComposeDisplay";
|
||||
import DetailDisplay from "./home/DetailDisplay";
|
||||
import Home from "./home/Home";
|
||||
|
||||
const Layout = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
homeDisplay: "inline-block",
|
||||
detailDisplay: "none",
|
||||
composeDisplay: "none",
|
||||
detailData: "",
|
||||
pods: [],
|
||||
racks: [],
|
||||
systems: [],
|
||||
storage: [],
|
||||
nodes: []
|
||||
};
|
||||
},
|
||||
|
||||
displayHome: function() {
|
||||
this.setState({
|
||||
homeDisplay: "inline-block",
|
||||
detailDisplay: "none",
|
||||
composeDisplay: "none",
|
||||
detailData: ""
|
||||
});
|
||||
},
|
||||
|
||||
displayDetail: function(item) {
|
||||
this.setState({
|
||||
homeDisplay: "none",
|
||||
detailDisplay: "inline-block",
|
||||
composeDisplay: "none",
|
||||
detailData: JSON.stringify(item, null, "\t")
|
||||
});
|
||||
},
|
||||
|
||||
displayCompose: function() {
|
||||
this.setState({
|
||||
homeDisplay: "none",
|
||||
detailDisplay: "none",
|
||||
composeDisplay: "inline-block",
|
||||
detailData: ""
|
||||
});
|
||||
},
|
||||
|
||||
updatePods: function(pods) {
|
||||
this.setState({pods: pods});
|
||||
},
|
||||
|
||||
updateRacks: function(racks) {
|
||||
this.setState({racks: racks});
|
||||
},
|
||||
|
||||
updateSystems: function(systems) {
|
||||
this.setState({systems: systems});
|
||||
},
|
||||
|
||||
updateStorage: function(storage) {
|
||||
this.setState({storage: storage});
|
||||
},
|
||||
|
||||
updateNodes: function(nodes) {
|
||||
this.setState({nodes: nodes});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">Rack Scale Design</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="#">Home</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Configure <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#">Action</a></li>
|
||||
<li><a href="#">Another action</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="dropdown-header">Nav header</li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
<li><a href="#">One more separated link</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">Support</a></li>
|
||||
<li><a href="#">About</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="active"><a href="./">Login<span class="sr-only">(current)</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<Home
|
||||
display={this.state.homeDisplay}
|
||||
podList={this.state.pods}
|
||||
rackList={this.state.racks}
|
||||
systemList={this.state.systems}
|
||||
storageList={this.state.storage}
|
||||
nodeList={this.state.nodes}
|
||||
onShowDetail={this.displayDetail}
|
||||
onShowCompose={this.displayCompose}
|
||||
onUpdatePods={this.updatePods}
|
||||
onUpdateRacks={this.updateRacks}
|
||||
onUpdateSystems={this.updateSystems}
|
||||
onUpdateStorage={this.updateStorage}
|
||||
onUpdateNodes={this.updateNodes}
|
||||
/>
|
||||
<DetailDisplay
|
||||
display={this.state.detailDisplay}
|
||||
data={this.state.detailData}
|
||||
onHideDetail={this.displayHome}
|
||||
/>
|
||||
<ComposeDisplay
|
||||
display={this.state.composeDisplay}
|
||||
systemList={this.state.systems}
|
||||
onHideCompose={this.displayHome}
|
||||
/>
|
||||
|
||||
<footer class="footer navbar-fixed-bottom">
|
||||
<div class="container">
|
||||
<p class="text-muted">Version: 0.1</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Layout;
|
122
valence/ui/src/js/components/home/ComposeDisplay.js
Normal file
122
valence/ui/src/js/components/home/ComposeDisplay.js
Normal file
@ -0,0 +1,122 @@
|
||||
import React from "react";
|
||||
|
||||
var config = require('../../config.js');
|
||||
var util = require('../../util.js');
|
||||
|
||||
const ComposeDisplay = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
processors: []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.getProcessors();
|
||||
},
|
||||
|
||||
compose: function() {
|
||||
var data = this.prepareRequest();
|
||||
var url = config.url + '/redfish/v1/Nodes/Actions/Allocate';
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: data,
|
||||
dataType: 'text',
|
||||
error: function(xhr, status, err) {
|
||||
console.error(url, status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
this.clearInputs()
|
||||
this.props.onHideCompose();
|
||||
},
|
||||
|
||||
getProcessors: function() {
|
||||
util.getProcessors(this.props.systemList, this.setProcessors);
|
||||
},
|
||||
|
||||
setProcessors: function(processors) {
|
||||
this.setState({processors: processors});
|
||||
this.fillForms();
|
||||
},
|
||||
|
||||
fillForms: function() {
|
||||
var sel = document.getElementById('procModels');
|
||||
sel.innerHTML = "";
|
||||
for (var i = 0; i < this.state.processors.length; i++) {
|
||||
if (this.state.processors[i]['Model']) {
|
||||
var opt = document.createElement('option');
|
||||
opt.innerHTML = this.state.processors[i]['Model'];
|
||||
opt.value = this.state.processors[i]['Model'];
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
prepareRequest: function() {
|
||||
var name = document.getElementById('name').value;
|
||||
var description = document.getElementById('description').value;
|
||||
var totalMem = document.getElementById('totalMem').value;
|
||||
var procModel = document.getElementById('procModels').value;
|
||||
if (procModel == "") {
|
||||
procModel = null;
|
||||
}
|
||||
var data = {
|
||||
"Name": name,
|
||||
"Description": description,
|
||||
"Memory": [{
|
||||
"CapacityMiB": totalMem * 1000
|
||||
}],
|
||||
"Processors": [{
|
||||
"Model": procModel
|
||||
}]
|
||||
}
|
||||
return JSON.stringify(data);
|
||||
},
|
||||
|
||||
clearInputs: function() {
|
||||
document.getElementById("inputForm").reset();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div class="details" style={{display: this.props.display}}>
|
||||
<form id="inputForm">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="right">Name:</td>
|
||||
<td align="left"><input type="text" id="name" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Description:</td>
|
||||
<td align="left"><input type="text" id="description" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">System Memory GB:</td>
|
||||
<td align="left"><input type="number" min="0" id="totalMem" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Processor Model:</td>
|
||||
<td align="left"><select id="procModels" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<input type="button"
|
||||
class="compose-button"
|
||||
onClick={() => this.compose()} value="Compose" />
|
||||
<input type="button"
|
||||
class="detail-button"
|
||||
onClick={() => this.props.onHideCompose()} value="Return" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default ComposeDisplay
|
17
valence/ui/src/js/components/home/DetailDisplay.js
Normal file
17
valence/ui/src/js/components/home/DetailDisplay.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
const DetailDisplay = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div class="details" style={{display: this.props.display}}>
|
||||
<pre>{this.props.data}</pre>
|
||||
<input type="button"
|
||||
class="detail-button"
|
||||
onClick={() => this.props.onHideDetail()} value="Return" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default DetailDisplay
|
153
valence/ui/src/js/components/home/Home.js
Normal file
153
valence/ui/src/js/components/home/Home.js
Normal file
@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import ResourceList from "./ResourceList";
|
||||
import NodeList from "./NodeList";
|
||||
|
||||
var config = require('../../config.js');
|
||||
var util = require('../../util.js');
|
||||
|
||||
const Home = React.createClass({
|
||||
|
||||
configCompose: function() {
|
||||
/* This is a temporary function that will compose a node based on the JSON value
|
||||
* of the nodeConfig variable in config.js.
|
||||
*
|
||||
* TODO(ntpttr): Remove this once the compose menu is fully flushed out.
|
||||
*/
|
||||
var url = config.url + '/redfish/v1/Nodes/Actions/Allocate';
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(config.nodeConfig),
|
||||
dataType: 'text',
|
||||
success: function(resp) {
|
||||
this.getNodes();
|
||||
}.bind(this),
|
||||
error: function(xhr, status, err) {
|
||||
console.error(url, status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.getPods();
|
||||
this.getRacks();
|
||||
this.getSystems();
|
||||
this.getStorage();
|
||||
this.getNodes();
|
||||
},
|
||||
|
||||
getPods: function() {
|
||||
util.getPods(this.setPods);
|
||||
},
|
||||
|
||||
getRacks: function() {
|
||||
util.getRacks(this.setRacks);
|
||||
},
|
||||
|
||||
getSystems: function() {
|
||||
util.getSystems(this.setSystems);
|
||||
},
|
||||
|
||||
getStorage: function() {
|
||||
util.getStorage(this.setStorage);
|
||||
},
|
||||
|
||||
getNodes: function() {
|
||||
util.getNodes(this.setNodes);
|
||||
},
|
||||
|
||||
setPods: function(pods) {
|
||||
this.props.onUpdatePods(pods);
|
||||
},
|
||||
|
||||
setRacks: function(racks) {
|
||||
this.props.onUpdateRacks(racks);
|
||||
},
|
||||
|
||||
setSystems: function(systems) {
|
||||
this.props.onUpdateSystems(systems);
|
||||
},
|
||||
|
||||
setStorage: function(storage) {
|
||||
this.props.onUpdateStorage(storage);
|
||||
},
|
||||
|
||||
setNodes: function(nodes) {
|
||||
this.props.onUpdateNodes(nodes);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div style={{display: this.props.display}}>
|
||||
<div class="jumbotron">
|
||||
<h2>Welcome to RSD Details</h2>
|
||||
<p>This is a brief overview of all kinds of resources in this environment. See the <a href="#">User Guide</a> for more information on how to configure them.</p>
|
||||
<p>
|
||||
<input type="button" class="btn btn-lg btn-primary" style={{marginRight:'20px'}} onClick={() => this.props.onShowCompose()} value="Compose Node" />
|
||||
<input type="button" class="btn btn-lg btn-primary" onClick={() => this.configCompose()} value="Compose From Config File" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="dashboard">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-md-2 sidebar">
|
||||
<ul class="nav nav-sidebar">
|
||||
<li class="active"><a href="#pods" data-toggle="tab" onClick={() => this.getPods()}>PODS</a></li>
|
||||
<li><a href="#racks" data-toggle="tab" onClick={() => this.getRacks()}>RACKS</a></li>
|
||||
<li><a href="#systems" data-toggle="tab" onClick={() => this.getSystems()}>SYSTEMS</a></li>
|
||||
<li><a href="#storage" data-toggle="tab" onClick={() => this.getStorage()}>STORAGE</a></li>
|
||||
<li><a href="#composednodes" data-toggle="tab" onClick={() => this.getNodes()}>COMPOSED NODES</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-9 col-md-10 main">
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="pods">
|
||||
<ResourceList
|
||||
onShowDetail={this.props.onShowDetail}
|
||||
resources={this.props.podList}
|
||||
header="PODS"
|
||||
/>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="racks">
|
||||
<ResourceList
|
||||
onShowDetail={this.props.onShowDetail}
|
||||
resources={this.props.rackList}
|
||||
header="RACKS"
|
||||
/>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="systems">
|
||||
<ResourceList
|
||||
onShowDetail={this.props.onShowDetail}
|
||||
resources={this.props.systemList}
|
||||
header="SYSTEMS"
|
||||
/>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="storage">
|
||||
<ResourceList
|
||||
onShowDetail={this.props.onShowDetail}
|
||||
resources={this.props.storageList}
|
||||
header="STORAGE"
|
||||
/>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="composednodes">
|
||||
<NodeList
|
||||
onShowDetail={this.props.onShowDetail}
|
||||
onUpdateNodes={this.getNodes}
|
||||
nodes={this.props.nodeList}
|
||||
header="COMPOSED NODES"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Home
|
86
valence/ui/src/js/components/home/NodeList.js
Normal file
86
valence/ui/src/js/components/home/NodeList.js
Normal file
@ -0,0 +1,86 @@
|
||||
import React from "react";
|
||||
|
||||
var config = require('../../config.js');
|
||||
var util = require('../../util.js');
|
||||
|
||||
const NodeList = React.createClass({
|
||||
|
||||
delete: function(nodeId) {
|
||||
var url = config.url + '/redfish/v1/Nodes/' + nodeId;
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'DELETE',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: function(resp) {
|
||||
this.props.onUpdateNodes();
|
||||
}.bind(this),
|
||||
error: function(xhr, status, err) {
|
||||
console.error(url, status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
assemble: function(nodeId) {
|
||||
var url = config.url + '/redfish/v1/Nodes/' + nodeId + '/Actions/ComposedNode.Assemble'
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
success: function(resp) {
|
||||
this.props.onUpdateNodes();
|
||||
}.bind(this),
|
||||
error: function(xhr, status, err) {
|
||||
console.error(url, status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
powerOn: function(nodeId) {
|
||||
var url = config.url + '/redfish/v1/Nodes/' + nodeId + '/Actions/ComposedNode.Reset'
|
||||
console.log(nodeId);
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify({"ResetType": "On"}),
|
||||
success: function(resp) {
|
||||
console.log(resp);
|
||||
this.props.onUpdateNodes();
|
||||
}.bind(this),
|
||||
error: function(xhr, status, err) {
|
||||
console.error(url, status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
renderList: function() {
|
||||
return this.props.nodes.map((node, i) =>
|
||||
<div class="item" key={i}>
|
||||
{node.Name}
|
||||
<input type="button" class="detail-button" onClick={() => this.props.onShowDetail(node)} value="Show" />
|
||||
<input type="button" class="detail-button" onClick={() => this.delete(node.Id)} value="Delete" />
|
||||
<input type="button" class="detail-button" onClick={() => this.assemble(node.Id)} value="Assemble" />
|
||||
<input type="button" class="detail-button" onClick={() => this.powerOn(node.Id)} value="Power On" />
|
||||
<br />
|
||||
{node.Description}
|
||||
<hr class="separator"/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderList()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
NodeList.defaultProps = { nodes: [], header: ""};
|
||||
|
||||
export default NodeList;
|
30
valence/ui/src/js/components/home/ResourceList.js
Normal file
30
valence/ui/src/js/components/home/ResourceList.js
Normal file
@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
|
||||
var util = require('../../util.js');
|
||||
|
||||
const ResourceList = React.createClass({
|
||||
|
||||
renderList: function() {
|
||||
return this.props.resources.map((resource, i) =>
|
||||
<div class="resource" key={i}>
|
||||
{resource.Name}
|
||||
<input type="button" class="detail-button" onClick={() => this.props.onShowDetail(resource)} value="Show" />
|
||||
<br />
|
||||
{resource.Description}
|
||||
<hr class="separator"/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderList()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
ResourceList.defaultProps = { resources: [], header: ""};
|
||||
|
||||
export default ResourceList;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user