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