Initial import from external repository

External repo: https://github.com/mganguli/RSC
Commit: 49199a82045f1d6f231eb477de3dbcd59492e9d9

Change-Id: I9eaec387605a39ba5e4c571026cacb1845938231
This commit is contained in:
Lin Yang 2016-10-07 08:36:35 +08:00
parent 5eceb8d3e8
commit 0ac90c5522
105 changed files with 4937 additions and 0 deletions

70
.gitignore vendored Normal file
View 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
View 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
View File

@ -0,0 +1,4 @@
plasma Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

7
MANIFEST.in Normal file
View File

@ -0,0 +1,7 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc
#recursive-include public *

91
README.rst Normal file
View 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``

1
babel.cfg Normal file
View File

@ -0,0 +1 @@
[python: **.py]

3
doc/README.md Normal file
View File

@ -0,0 +1,3 @@
RSC API spec and RSC mockup file.
<chester.kuo@intel.com>

30
doc/api-mockup/index.json Normal file
View 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"
}
]
}

View 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"
}
]
}

View 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"}
]
}
]
}

View 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"
}
]
}
}

View 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"
}
]
}

View File

@ -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"
}
]
}
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}
}

View 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"
}
]
}
]
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View 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"
}
]
}
]
}

Binary file not shown.

75
doc/source/conf.py Normal file
View 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}

View File

@ -0,0 +1,4 @@
============
Contributing
============
.. include:: ../../CONTRIBUTING.rst

24
doc/source/index.rst Normal file
View 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`

View 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

View 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

View 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
View File

@ -0,0 +1 @@
.. include:: ../../README.rst

7
doc/source/usage.rst Normal file
View File

@ -0,0 +1,7 @@
========
Usage
========
To use plasma in a project::
import plasma

View 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" : { }
}
```

View 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

View 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
View 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"

View File

View File

272
releasenotes/source/conf.py Normal file
View 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

View File

@ -0,0 +1,8 @@
============================================
plasma Release Notes
============================================
.. toctree::
:maxdepth: 1
unreleased

View File

@ -0,0 +1,5 @@
==============================
Current Series Release Notes
==============================
.. release-notes::

41
requirements.txt Normal file
View 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
View 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
View 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
View 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
View 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
View File

0
valence/api/__init__.py Normal file
View File

61
valence/api/app.py Normal file
View 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
View 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

View File

View 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()

View 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)

View 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())

View 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)

View File

View 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())

View 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

View 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)

View 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
View 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
View File

49
valence/cmd/api.py Executable file
View 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
View 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()

View File

75
valence/common/context.py Normal file
View 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()

View 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")

View 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

View File

View 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

View 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)

View 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
View 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)

View 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)

View File

67
valence/controller/api.py Normal file
View 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')

View 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))

View File

View 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)

View 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"}

View File

54
valence/flavor/flavor.py Normal file
View 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

View 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()

View 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)]
"""

View 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)
}

View 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)
}

View 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"}

View File

63
valence/objects/base.py Normal file
View 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
View 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
View 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

View 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

View 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
View 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
View 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"
}

View 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
View 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>

View 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;

View 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

View 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

View 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

View 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;

View 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