initial commit
This commit is contained in:
commit
2c60a61452
11
CA/.gitignore
vendored
Normal file
11
CA/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
index.txt
|
||||||
|
index.txt.old
|
||||||
|
index.txt.attr
|
||||||
|
index.txt.attr.old
|
||||||
|
cacert.pem
|
||||||
|
serial
|
||||||
|
serial.old
|
||||||
|
openssl.cnf
|
||||||
|
private/*
|
||||||
|
newcerts/*
|
||||||
|
|
1
CA/INTER/.gitignore
vendored
Normal file
1
CA/INTER/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*
|
30
CA/geninter.sh
Executable file
30
CA/geninter.sh
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
# ARG is the id of the user
|
||||||
|
|
||||||
|
mkdir INTER/$1
|
||||||
|
cd INTER/$1
|
||||||
|
cp ../../openssl.cnf.tmpl openssl.cnf
|
||||||
|
sed -i -e s/%USERNAME%/$1/g openssl.cnf
|
||||||
|
mkdir certs crl newcerts private
|
||||||
|
echo "10" > serial
|
||||||
|
touch index.txt
|
||||||
|
openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
|
||||||
|
openssl req -new -sha1 -key private/cakey.pem -out ../../reqs/inter$1.csr -batch -subj "/C=US/ST=California/L=Mountain View/O=Anso Labs/OU=Nova Dev/CN=customer-intCA-$1"
|
||||||
|
cd ../../
|
||||||
|
openssl ca -extensions v3_ca -days 365 -out INTER/$1/cacert.pem -in reqs/inter$1.csr -config openssl.cnf -batch
|
26
CA/genrootca.sh
Executable file
26
CA/genrootca.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
if [ -f "cacert.pem" ];
|
||||||
|
then
|
||||||
|
echo "Not installing, it's already done."
|
||||||
|
else
|
||||||
|
cp openssl.cnf.tmpl openssl.cnf
|
||||||
|
sed -i -e s/%USERNAME%/ROOT/g openssl.cnf
|
||||||
|
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
|
||||||
|
touch index.txt
|
||||||
|
echo "10" > serial
|
||||||
|
fi
|
0
CA/newcerts/.placeholder
Normal file
0
CA/newcerts/.placeholder
Normal file
87
CA/openssl.cnf.tmpl
Normal file
87
CA/openssl.cnf.tmpl
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
#
|
||||||
|
# OpenSSL configuration file.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Establish working directory.
|
||||||
|
|
||||||
|
dir = .
|
||||||
|
|
||||||
|
[ ca ]
|
||||||
|
default_ca = CA_default
|
||||||
|
unique_subject = no
|
||||||
|
|
||||||
|
[ CA_default ]
|
||||||
|
serial = $dir/serial
|
||||||
|
database = $dir/index.txt
|
||||||
|
new_certs_dir = $dir/newcerts
|
||||||
|
certificate = $dir/cacert.pem
|
||||||
|
private_key = $dir/private/cakey.pem
|
||||||
|
default_days = 365
|
||||||
|
default_md = md5
|
||||||
|
preserve = no
|
||||||
|
email_in_dn = no
|
||||||
|
nameopt = default_ca
|
||||||
|
certopt = default_ca
|
||||||
|
policy = policy_match
|
||||||
|
|
||||||
|
[ policy_match ]
|
||||||
|
countryName = match
|
||||||
|
stateOrProvinceName = match
|
||||||
|
organizationName = optional
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
|
||||||
|
|
||||||
|
[ req ]
|
||||||
|
default_bits = 1024 # Size of keys
|
||||||
|
default_keyfile = key.pem # name of generated keys
|
||||||
|
default_md = md5 # message digest algorithm
|
||||||
|
string_mask = nombstr # permitted characters
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
|
||||||
|
[ req_distinguished_name ]
|
||||||
|
# Variable name Prompt string
|
||||||
|
#---------------------- ----------------------------------
|
||||||
|
0.organizationName = Organization Name (company)
|
||||||
|
organizationalUnitName = Organizational Unit Name (department, division)
|
||||||
|
emailAddress = Email Address
|
||||||
|
emailAddress_max = 40
|
||||||
|
localityName = Locality Name (city, district)
|
||||||
|
stateOrProvinceName = State or Province Name (full name)
|
||||||
|
countryName = Country Name (2 letter code)
|
||||||
|
countryName_min = 2
|
||||||
|
countryName_max = 2
|
||||||
|
commonName = Common Name (hostname, IP, or your name)
|
||||||
|
commonName_max = 64
|
||||||
|
|
||||||
|
# Default values for the above, for consistency and less typing.
|
||||||
|
# Variable name Value
|
||||||
|
#------------------------------ ------------------------------
|
||||||
|
0.organizationName_default = NOVA %USERNAME%
|
||||||
|
localityName_default = Mountain View
|
||||||
|
stateOrProvinceName_default = California
|
||||||
|
countryName_default = US
|
||||||
|
|
||||||
|
[ v3_ca ]
|
||||||
|
basicConstraints = CA:TRUE
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always,issuer:always
|
||||||
|
|
||||||
|
[ v3_req ]
|
||||||
|
basicConstraints = CA:FALSE
|
||||||
|
subjectKeyIdentifier = hash
|
0
CA/private/.placeholder
Normal file
0
CA/private/.placeholder
Normal file
1
CA/reqs/.gitignore
vendored
Normal file
1
CA/reqs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*
|
53
HACKING
Normal file
53
HACKING
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
Nova Style Commandments
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Step 1: Read http://www.python.org/dev/peps/pep-0008/
|
||||||
|
Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
|
||||||
|
Step 3: Read on
|
||||||
|
|
||||||
|
Imports
|
||||||
|
-------
|
||||||
|
- thou shalt not import objects, only modules
|
||||||
|
- thou shalt not import more than one module per line
|
||||||
|
- thou shalt not make relative imports
|
||||||
|
- thou shalt "from nova import vendor" before importing third party code
|
||||||
|
- thou shalt organize your imports according to the following template
|
||||||
|
|
||||||
|
::
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
{{stdlib imports in human alphabetical order}}
|
||||||
|
\n
|
||||||
|
from nova import vendor
|
||||||
|
{{vendor imports in human alphabetical order}}
|
||||||
|
\n
|
||||||
|
{{nova imports in human alphabetical order}}
|
||||||
|
\n
|
||||||
|
\n
|
||||||
|
{{begin your code}}
|
||||||
|
|
||||||
|
|
||||||
|
General
|
||||||
|
-------
|
||||||
|
- thou shalt put two newlines twixt toplevel code (funcs, classes, etc)
|
||||||
|
- thou shalt put one newline twixt methods in classes and anywhere else
|
||||||
|
- thou shalt not write "except:", use "except Exception:" at the very least
|
||||||
|
- thou shalt include your name with TODOs as in "TODO(termie)"
|
||||||
|
- thou shalt not name anything the same name as a builtin or reserved word
|
||||||
|
- thou shalt not violate causality in our time cone, or else
|
||||||
|
|
||||||
|
|
||||||
|
Human Alphabetical Order Examples
|
||||||
|
---------------------------------
|
||||||
|
::
|
||||||
|
import httplib
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import StringIO
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import test
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.endpoint import api
|
||||||
|
from nova.endpoint import cloud
|
176
LICENSE
Normal file
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
63
bin/nova-api
Executable file
63
bin/nova-api
Executable file
@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
Tornado daemon for the main API endpoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from tornado import httpserver
|
||||||
|
from tornado import ioloop
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import rpc
|
||||||
|
from nova import server
|
||||||
|
from nova import utils
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.endpoint import admin
|
||||||
|
from nova.endpoint import api
|
||||||
|
from nova.endpoint import cloud
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
def main(_argv):
|
||||||
|
user_manager = users.UserManager()
|
||||||
|
controllers = {
|
||||||
|
'Cloud': cloud.CloudController(),
|
||||||
|
'Admin': admin.AdminController(user_manager)
|
||||||
|
}
|
||||||
|
_app = api.APIServerApplication(user_manager, controllers)
|
||||||
|
|
||||||
|
conn = rpc.Connection.instance()
|
||||||
|
consumer = rpc.AdapterConsumer(connection=conn,
|
||||||
|
topic=FLAGS.cloud_topic,
|
||||||
|
proxy=controllers['Cloud'])
|
||||||
|
|
||||||
|
io_inst = ioloop.IOLoop.instance()
|
||||||
|
_injected = consumer.attach_to_tornado(io_inst)
|
||||||
|
|
||||||
|
http_server = httpserver.HTTPServer(_app)
|
||||||
|
http_server.listen(FLAGS.cc_port)
|
||||||
|
logging.debug('Started HTTP server on %s', FLAGS.cc_port)
|
||||||
|
io_inst.start()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
utils.default_flagfile()
|
||||||
|
server.serve('nova-api', main)
|
97
bin/nova-compute
Executable file
97
bin/nova-compute
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
Twistd daemon for the nova compute nodes.
|
||||||
|
Receives messages via AMQP, manages pool of worker threads
|
||||||
|
for async tasks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# NOTE(termie): kludge so that we can run this from the bin directory in the
|
||||||
|
# checkout without having to screw with paths
|
||||||
|
NOVA_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'nova')
|
||||||
|
if os.path.exists(NOVA_PATH):
|
||||||
|
sys.path.insert(0, os.path.dirname(NOVA_PATH))
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from carrot import connection
|
||||||
|
from carrot import messaging
|
||||||
|
from twisted.internet import task
|
||||||
|
from twisted.application import service
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import rpc
|
||||||
|
from nova import twistd
|
||||||
|
from nova.compute import node
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
# NOTE(termie): This file will necessarily be re-imported under different
|
||||||
|
# context when the twistd.serve() call is made below so any
|
||||||
|
# flags we define here will have to be conditionally defined,
|
||||||
|
# flags defined by imported modules are safe.
|
||||||
|
if 'node_report_state_interval' not in FLAGS:
|
||||||
|
flags.DEFINE_integer('node_report_state_interval', 10,
|
||||||
|
'seconds between nodes reporting state to cloud',
|
||||||
|
lower_bound=1)
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.warn('Starting compute node')
|
||||||
|
n = node.NetworkNode()
|
||||||
|
d = n.adopt_instances()
|
||||||
|
d.addCallback(lambda x: logging.info('Adopted %d instances', x))
|
||||||
|
|
||||||
|
conn = rpc.Connection.instance()
|
||||||
|
consumer_all = rpc.AdapterConsumer(
|
||||||
|
connection=conn,
|
||||||
|
topic='%s' % FLAGS.compute_topic,
|
||||||
|
proxy=n)
|
||||||
|
|
||||||
|
consumer_node = rpc.AdapterConsumer(
|
||||||
|
connection=conn,
|
||||||
|
topic='%s.%s' % (FLAGS.compute_topic, FLAGS.node_name),
|
||||||
|
proxy=n)
|
||||||
|
|
||||||
|
# heartbeat = task.LoopingCall(n.report_state)
|
||||||
|
# heartbeat.start(interval=FLAGS.node_report_state_interval, now=False)
|
||||||
|
|
||||||
|
injected = consumer_all.attach_to_twisted()
|
||||||
|
injected = consumer_node.attach_to_twisted()
|
||||||
|
|
||||||
|
# This is the parent service that twistd will be looking for when it
|
||||||
|
# parses this file, return it so that we can get it into globals below
|
||||||
|
application = service.Application('nova-compute')
|
||||||
|
n.setServiceParent(application)
|
||||||
|
return application
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(termie): When this script is executed from the commandline what it will
|
||||||
|
# actually do is tell the twistd application runner that it
|
||||||
|
# should run this file as a twistd application (see below).
|
||||||
|
if __name__ == '__main__':
|
||||||
|
twistd.serve(__file__)
|
||||||
|
|
||||||
|
# NOTE(termie): When this script is loaded by the twistd application runner
|
||||||
|
# this code path will be executed and twistd will expect a
|
||||||
|
# variable named 'application' to be available, it will then
|
||||||
|
# handle starting it and stopping it.
|
||||||
|
if __name__ == '__builtin__':
|
||||||
|
application = main()
|
158
bin/nova-manage
Executable file
158
bin/nova-manage
Executable file
@ -0,0 +1,158 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
CLI interface for nova management.
|
||||||
|
Connects to the running ADMIN api in the api daemon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import utils
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.compute import model
|
||||||
|
from nova.endpoint import cloud
|
||||||
|
import time
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class UserCommands(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.manager = users.UserManager.instance()
|
||||||
|
|
||||||
|
def __print_export(self, user):
|
||||||
|
print 'export EC2_ACCESS_KEY=%s' % user.access
|
||||||
|
print 'export EC2_SECRET_KEY=%s' % user.secret
|
||||||
|
|
||||||
|
def admin(self, name, access=None, secret=None):
|
||||||
|
"""creates a new admin and prints exports
|
||||||
|
arguments: name [access] [secret]"""
|
||||||
|
user = self.manager.create_user(name, access, secret, True)
|
||||||
|
self.__print_export(user)
|
||||||
|
|
||||||
|
def create(self, name, access=None, secret=None):
|
||||||
|
"""creates a new user and prints exports
|
||||||
|
arguments: name [access] [secret]"""
|
||||||
|
user = self.manager.create_user(name, access, secret, False)
|
||||||
|
self.__print_export(user)
|
||||||
|
|
||||||
|
def delete(self, name):
|
||||||
|
"""deletes an existing user
|
||||||
|
arguments: name"""
|
||||||
|
self.manager.delete_user(name)
|
||||||
|
|
||||||
|
def exports(self, name):
|
||||||
|
"""prints access and secrets for user in export format
|
||||||
|
arguments: name"""
|
||||||
|
user = self.manager.get_user(name)
|
||||||
|
if user:
|
||||||
|
self.__print_export(user)
|
||||||
|
else:
|
||||||
|
print "User %s doesn't exist" % name
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
"""lists all users
|
||||||
|
arguments: <none>"""
|
||||||
|
for user in self.manager.get_users():
|
||||||
|
print user.name
|
||||||
|
|
||||||
|
def zip(self, name, filename='nova.zip'):
|
||||||
|
"""exports credentials for user to a zip file
|
||||||
|
arguments: name [filename='nova.zip]"""
|
||||||
|
user = self.manager.get_user(name)
|
||||||
|
if user:
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(user.get_credentials())
|
||||||
|
else:
|
||||||
|
print "User %s doesn't exist" % name
|
||||||
|
|
||||||
|
|
||||||
|
def usage(script_name):
|
||||||
|
print script_name + " category action [<args>]"
|
||||||
|
|
||||||
|
|
||||||
|
categories = [
|
||||||
|
('user', UserCommands),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def lazy_match(name, key_value_tuples):
|
||||||
|
"""finds all objects that have a key that case insensitively contains [name]
|
||||||
|
key_value_tuples is a list of tuples of the form (key, value)
|
||||||
|
returns a list of tuples of the form (key, value)"""
|
||||||
|
return [(k, v) for (k, v) in key_value_tuples if k.lower().find(name.lower()) == 0]
|
||||||
|
|
||||||
|
|
||||||
|
def methods_of(obj):
|
||||||
|
"""get all callable methods of an object that don't start with underscore
|
||||||
|
returns a list of tuples of the form (method_name, method)"""
|
||||||
|
return [(i, getattr(obj, i)) for i in dir(obj) if callable(getattr(obj, i)) and not i.startswith('_')]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
utils.default_flagfile()
|
||||||
|
argv = FLAGS(sys.argv)
|
||||||
|
script_name = argv.pop(0)
|
||||||
|
if len(argv) < 1:
|
||||||
|
usage(script_name)
|
||||||
|
print "Available categories:"
|
||||||
|
for k, v in categories:
|
||||||
|
print "\t%s" % k
|
||||||
|
sys.exit(2)
|
||||||
|
category = argv.pop(0)
|
||||||
|
matches = lazy_match(category, categories)
|
||||||
|
if len(matches) == 0:
|
||||||
|
print "%s does not match any categories:" % category
|
||||||
|
for k, v in categories:
|
||||||
|
print "\t%s" % k
|
||||||
|
sys.exit(2)
|
||||||
|
if len(matches) > 1:
|
||||||
|
print "%s matched multiple categories:" % category
|
||||||
|
for k, v in matches:
|
||||||
|
print "\t%s" % k
|
||||||
|
sys.exit(2)
|
||||||
|
# instantiate the command group object
|
||||||
|
category, fn = matches[0]
|
||||||
|
command_object = fn()
|
||||||
|
actions = methods_of(command_object)
|
||||||
|
if len(argv) < 1:
|
||||||
|
usage(script_name)
|
||||||
|
print "Available actions for %s category:" % category
|
||||||
|
for k, v in actions:
|
||||||
|
print "\t%s" % k
|
||||||
|
sys.exit(2)
|
||||||
|
action = argv.pop(0)
|
||||||
|
matches = lazy_match(action, actions)
|
||||||
|
if len(matches) == 0:
|
||||||
|
print "%s does not match any actions" % action
|
||||||
|
for k, v in actions:
|
||||||
|
print "\t%s" % k
|
||||||
|
sys.exit(2)
|
||||||
|
if len(matches) > 1:
|
||||||
|
print "%s matched multiple actions:" % action
|
||||||
|
for k, v in matches:
|
||||||
|
print "\t%s" % k
|
||||||
|
sys.exit(2)
|
||||||
|
action, fn = matches[0]
|
||||||
|
# call the action with the remaining arguments
|
||||||
|
try:
|
||||||
|
fn(*argv)
|
||||||
|
except TypeError:
|
||||||
|
print "Wrong number of arguments supplied"
|
||||||
|
print "%s %s: %s" % (category, action, fn.__doc__)
|
||||||
|
|
49
bin/nova-objectstore
Executable file
49
bin/nova-objectstore
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
Tornado daemon for nova objectstore. Supports S3 API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from tornado import httpserver
|
||||||
|
from tornado import ioloop
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import server
|
||||||
|
from nova import utils
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.objectstore import handler
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
# FIXME: if this log statement isn't here, no logging
|
||||||
|
# appears from other files and app won't start daemonized
|
||||||
|
logging.debug('Started HTTP server on %s' % (FLAGS.s3_internal_port))
|
||||||
|
app = handler.Application(users.UserManager())
|
||||||
|
server = httpserver.HTTPServer(app)
|
||||||
|
server.listen(FLAGS.s3_internal_port)
|
||||||
|
ioloop.IOLoop.instance().start()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
utils.default_flagfile()
|
||||||
|
server.serve('nova-objectstore', main)
|
68
bin/nova-volume
Executable file
68
bin/nova-volume
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
Tornado Storage daemon manages AoE volumes via AMQP messaging.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from tornado import ioloop
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import rpc
|
||||||
|
from nova import server
|
||||||
|
from nova import utils
|
||||||
|
from nova.volume import storage
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_integer('storage_report_state_interval', 10,
|
||||||
|
'seconds between broadcasting state to cloud',
|
||||||
|
lower_bound=1)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
bs = storage.BlockStore()
|
||||||
|
|
||||||
|
conn = rpc.Connection.instance()
|
||||||
|
consumer_all = rpc.AdapterConsumer(
|
||||||
|
connection=conn,
|
||||||
|
topic='%s' % FLAGS.storage_topic,
|
||||||
|
proxy=bs)
|
||||||
|
|
||||||
|
consumer_node = rpc.AdapterConsumer(
|
||||||
|
connection=conn,
|
||||||
|
topic='%s.%s' % (FLAGS.storage_topic, FLAGS.node_name),
|
||||||
|
proxy=bs)
|
||||||
|
|
||||||
|
io_inst = ioloop.IOLoop.instance()
|
||||||
|
scheduler = ioloop.PeriodicCallback(
|
||||||
|
lambda: bs.report_state(),
|
||||||
|
FLAGS.storage_report_state_interval * 1000,
|
||||||
|
io_loop=io_inst)
|
||||||
|
|
||||||
|
injected = consumer_all.attachToTornado(io_inst)
|
||||||
|
injected = consumer_node.attachToTornado(io_inst)
|
||||||
|
scheduler.start()
|
||||||
|
io_inst.start()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
utils.default_flagfile()
|
||||||
|
server.serve('nova-volume', main)
|
||||||
|
|
6
debian/changelog
vendored
Normal file
6
debian/changelog
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
nova (0.3.0-1) UNRELEASED; urgency=low
|
||||||
|
|
||||||
|
* initial release
|
||||||
|
|
||||||
|
-- Jesse Andrews <jesse@ansolabs.com> Thur, 27 May 2010 12:28:00 -0700
|
||||||
|
|
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
7
|
40
debian/control
vendored
Normal file
40
debian/control
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
Source: nova
|
||||||
|
Section: net
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: Jesse Andrews <jesse@ansolabs.com>
|
||||||
|
Build-Depends: debhelper (>= 7)
|
||||||
|
Build-Depends-Indep: python-support
|
||||||
|
Standards-Version: 3.8.4
|
||||||
|
XS-Python-Version: 2.6
|
||||||
|
|
||||||
|
Package: nova-common
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${python:Depends}, aoetools, vlan, python-ipy, python-boto, python-m2crypto, python-pycurl, python-twisted, python-daemon, python-redis, python-carrot, python-lockfile, python-gflags, python-tornado, ${misc:Depends}
|
||||||
|
Provides: ${python:Provides}
|
||||||
|
Conflicts: nova
|
||||||
|
Description: Nova is a cloud
|
||||||
|
|
||||||
|
Package: nova-compute
|
||||||
|
Architecture: all
|
||||||
|
Depends: nova-common (= ${binary:Version}), kpartx, kvm, python-libvirt, libvirt-bin (>= 0.8.1), ${python:Depends}, ${misc:Depends}
|
||||||
|
Description: Nova compute
|
||||||
|
|
||||||
|
Package: nova-volume
|
||||||
|
Architecture: all
|
||||||
|
Depends: nova-common (= ${binary:Version}), vblade, vblade-persist, ${python:Depends}, ${misc:Depends}
|
||||||
|
Description: Nova volume
|
||||||
|
|
||||||
|
Package: nova-api
|
||||||
|
Architecture: all
|
||||||
|
Depends: nova-common (= ${binary:Version}), ${python:Depends}, ${misc:Depends}
|
||||||
|
Description: Nova api
|
||||||
|
|
||||||
|
Package: nova-objectstore
|
||||||
|
Architecture: all
|
||||||
|
Depends: nova-common (= ${binary:Version}), ${python:Depends}, ${misc:Depends}
|
||||||
|
Description: Nova object store
|
||||||
|
|
||||||
|
Package: nova-tools
|
||||||
|
Architecture: all
|
||||||
|
Depends: python-boto, ${python:Depends}, ${misc:Depends}
|
||||||
|
Description: CLI tools to access nova
|
69
debian/nova-api.init
vendored
Normal file
69
debian/nova-api.init
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: nova-api
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: nova-api
|
||||||
|
# Description: nova-api
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DAEMON=/usr/bin/nova-api
|
||||||
|
DAEMON_ARGS="--flagfile=/etc/nova.conf"
|
||||||
|
PIDFILE=/var/run/nova-api.pid
|
||||||
|
|
||||||
|
ENABLED=false
|
||||||
|
|
||||||
|
if test -f /etc/default/nova-api; then
|
||||||
|
. /etc/default/nova-api
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Starting nova api" "nova-api"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS start; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Stopping nova api" "nova-api"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS stop; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
restart|force-reload)
|
||||||
|
test "$ENABLED" = "true" || exit 1
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS restart; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
status_of_proc -p $PIDFILE $DAEMON nova-api && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_action_msg "Usage: /etc/init.d/nova-api {start|stop|restart|force-reload|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
1
debian/nova-api.install
vendored
Normal file
1
debian/nova-api.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
bin/nova-api usr/bin
|
4
debian/nova-common.install
vendored
Normal file
4
debian/nova-common.install
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
bin/nova-manage usr/bin
|
||||||
|
nova/auth/novarc.template usr/lib/pymodules/python2.6/nova/auth
|
||||||
|
nova/compute/libvirt.xml.template usr/lib/pymodules/python2.6/nova/compute
|
||||||
|
usr/lib/python*/*-packages/nova/*
|
69
debian/nova-compute.init
vendored
Normal file
69
debian/nova-compute.init
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: nova-compute
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: nova-compute
|
||||||
|
# Description: nova-compute
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DAEMON=/usr/bin/nova-compute
|
||||||
|
DAEMON_ARGS="--flagfile=/etc/nova.conf"
|
||||||
|
PIDFILE=/var/run/nova-compute.pid
|
||||||
|
|
||||||
|
ENABLED=false
|
||||||
|
|
||||||
|
if test -f /etc/default/nova-compute; then
|
||||||
|
. /etc/default/nova-compute
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Starting nova compute" "nova-compute"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS start; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Stopping nova compute" "nova-compute"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS stop; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
restart|force-reload)
|
||||||
|
test "$ENABLED" = "true" || exit 1
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS restart; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
status_of_proc -p $PIDFILE $DAEMON nova-compute && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_action_msg "Usage: /etc/init.d/nova-compute {start|stop|restart|force-reload|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
1
debian/nova-compute.install
vendored
Normal file
1
debian/nova-compute.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
bin/nova-compute usr/bin
|
69
debian/nova-objectstore.init
vendored
Normal file
69
debian/nova-objectstore.init
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: nova-objectstore
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: nova-objectstore
|
||||||
|
# Description: nova-objectstore
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DAEMON=/usr/bin/nova-objectstore
|
||||||
|
DAEMON_ARGS="--flagfile=/etc/nova.conf"
|
||||||
|
PIDFILE=/var/run/nova-objectstore.pid
|
||||||
|
|
||||||
|
ENABLED=false
|
||||||
|
|
||||||
|
if test -f /etc/default/nova-objectstore; then
|
||||||
|
. /etc/default/nova-objectstore
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Starting nova objectstore" "nova-objectstore"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS start; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Stopping nova objectstore" "nova-objectstore"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS stop; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
restart|force-reload)
|
||||||
|
test "$ENABLED" = "true" || exit 1
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS restart; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
status_of_proc -p $PIDFILE $DAEMON nova-objectstore && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_action_msg "Usage: /etc/init.d/nova-objectstore {start|stop|restart|force-reload|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
1
debian/nova-objectstore.install
vendored
Normal file
1
debian/nova-objectstore.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
bin/nova-objectstore usr/bin
|
69
debian/nova-volume.init
vendored
Normal file
69
debian/nova-volume.init
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: nova-volume
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: nova-volume
|
||||||
|
# Description: nova-volume
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DAEMON=/usr/bin/nova-volume
|
||||||
|
DAEMON_ARGS="--flagfile=/etc/nova.conf"
|
||||||
|
PIDFILE=/var/run/nova-volume.pid
|
||||||
|
|
||||||
|
ENABLED=false
|
||||||
|
|
||||||
|
if test -f /etc/default/nova-volume; then
|
||||||
|
. /etc/default/nova-volume
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Starting nova volume" "nova-volume"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS start; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
log_daemon_msg "Stopping nova volume" "nova-volume"
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS stop; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
restart|force-reload)
|
||||||
|
test "$ENABLED" = "true" || exit 1
|
||||||
|
cd /var/run
|
||||||
|
if $DAEMON $DAEMON_ARGS restart; then
|
||||||
|
log_end_msg 0
|
||||||
|
else
|
||||||
|
log_end_msg 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
test "$ENABLED" = "true" || exit 0
|
||||||
|
status_of_proc -p $PIDFILE $DAEMON nova-volume && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_action_msg "Usage: /etc/init.d/nova-volume {start|stop|restart|force-reload|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
1
debian/nova-volume.install
vendored
Normal file
1
debian/nova-volume.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
bin/nova-volume usr/bin
|
1
debian/pycompat
vendored
Normal file
1
debian/pycompat
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
2
|
1
debian/pyversions
vendored
Normal file
1
debian/pyversions
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
2.6-
|
4
debian/rules
vendored
Executable file
4
debian/rules
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
1
docs/.gitignore
vendored
Normal file
1
docs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
_build/*
|
89
docs/Makefile
Normal file
89
docs/Makefile
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nova.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nova.qhc"
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||||
|
"run these through (pdf)latex."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
1
docs/_build/.gitignore
vendored
Normal file
1
docs/_build/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*
|
0
docs/_static/.gitignore
vendored
Normal file
0
docs/_static/.gitignore
vendored
Normal file
0
docs/_templates/.gitignore
vendored
Normal file
0
docs/_templates/.gitignore
vendored
Normal file
46
docs/architecture.rst
Normal file
46
docs/architecture.rst
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
nova System Architecture
|
||||||
|
========================
|
||||||
|
|
||||||
|
Nova is built on a shared-nothing, messaging-based architecture. All of the major nova components can be run on multiple servers. This means that most component to component communication must go via message queue. In order to avoid blocking each component while waiting for a response, we use deferred objects, with a callback that gets triggered when a response is received.
|
||||||
|
|
||||||
|
In order to achieve shared-nothing with multiple copies of the same component (especially when the component is an API server that needs to reply with state information in a timely fashion), we need to keep all of our system state in a distributed data system. Updates to system state are written into this system, using atomic transactions when necessary. Requests for state are read out of this system. In limited cases, these read calls are memoized within controllers for short periods of time. (Such a limited case would be, for instance, the current list of system users.)
|
||||||
|
|
||||||
|
|
||||||
|
Components
|
||||||
|
----------
|
||||||
|
|
||||||
|
Below you will find a helpful explanation.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[ User Manager ] ---- ( LDAP )
|
||||||
|
|
|
||||||
|
| / [ Storage ] - ( ATAoE )
|
||||||
|
[ API server ] -> [ Cloud ] < AMQP >
|
||||||
|
| \ [ Nodes ] - ( libvirt/kvm )
|
||||||
|
< HTTP >
|
||||||
|
|
|
||||||
|
[ S3 ]
|
||||||
|
|
||||||
|
|
||||||
|
* API: receives http requests from boto, converts commands to/from API format, and sending requests to cloud controller
|
||||||
|
* Cloud Controller: global state of system, talks to ldap, s3, and node/storage workers through a queue
|
||||||
|
* Nodes: worker that spawns instances
|
||||||
|
* S3: tornado based http/s3 server
|
||||||
|
* User Manager: create/manage users, which are stored in ldap
|
||||||
|
* Network Controller: allocate and deallocate IPs and VLANs
|
213
docs/auth.rst
Normal file
213
docs/auth.rst
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Auth Documentation
|
||||||
|
==================
|
||||||
|
|
||||||
|
Nova provides RBAC (Role-based access control) of the AWS-type APIs. We define the following roles:
|
||||||
|
|
||||||
|
Roles-Based Access Control of AWS-style APIs using SAML Assertions
|
||||||
|
“Achieving FIPS 199 Moderate certification of a hybrid cloud environment using CloudAudit and declarative C.I.A. classifications”
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
--------------
|
||||||
|
|
||||||
|
We will investigate one method for integrating an AWS-style API with US eAuthentication-compatible federated authentication systems, to achieve access controls and limits based on traditional operational roles.
|
||||||
|
Additionally, we will look at how combining this approach, with an implementation of the CloudAudit APIs, will allow us to achieve a certification under FIPS 199 Moderate classification for a hybrid cloud environment.
|
||||||
|
|
||||||
|
Relationship of US eAuth to RBAC
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Typical implementations of US eAuth authentication systems are structured as follows::
|
||||||
|
|
||||||
|
[ MS Active Directory or other federated LDAP user store ]
|
||||||
|
--> backends to…
|
||||||
|
[ SUN Identity Manager or other SAML Policy Controller ]
|
||||||
|
--> maps URLs to groups…
|
||||||
|
[ Apache Policy Agent in front of eAuth-secured Web Application ]
|
||||||
|
|
||||||
|
In more ideal implementations, the remainder of the application-specific account information is stored either in extended schema on the LDAP server itself, via the use of a translucent LDAP proxy, or in an independent datastore keyed off of the UID provided via SAML assertion.
|
||||||
|
|
||||||
|
Basic AWS API call structure
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
AWS API calls are traditionally secured via Access and Secret Keys, which are used to sign API calls, along with traditional timestamps to prevent replay attacks. The APIs can be logically grouped into sets that align with five typical roles:
|
||||||
|
|
||||||
|
* System User
|
||||||
|
* System Administrator
|
||||||
|
* Network Administrator
|
||||||
|
* Project Manager
|
||||||
|
* Cloud Administrator
|
||||||
|
* (IT-Sec?)
|
||||||
|
|
||||||
|
There is an additional, conceptual end-user that may or may not have API access:
|
||||||
|
|
||||||
|
* (EXTERNAL) End-user / Third-party User
|
||||||
|
|
||||||
|
Basic operations are available to any System User:
|
||||||
|
|
||||||
|
* Launch Instance
|
||||||
|
* Terminate Instance (their own)
|
||||||
|
* Create keypair
|
||||||
|
* Delete keypair
|
||||||
|
* Create, Upload, Delete: Buckets and Keys (Object Store) – their own
|
||||||
|
* Create, Attach, Delete Volume (Block Store) – their own
|
||||||
|
|
||||||
|
System Administrators:
|
||||||
|
|
||||||
|
* Register/Unregister Machine Image (project-wide)
|
||||||
|
* Change Machine Image properties (public / private)
|
||||||
|
* Request / Review CloudAudit Scans
|
||||||
|
|
||||||
|
Network Administrator:
|
||||||
|
|
||||||
|
* Change Firewall Rules, define Security Groups
|
||||||
|
* Allocate, Associate, Deassociate Public IP addresses
|
||||||
|
|
||||||
|
Project Manager:
|
||||||
|
|
||||||
|
* Launch and Terminate Instances (project-wide)
|
||||||
|
* CRUD of Object and Block store (project-wide)
|
||||||
|
|
||||||
|
Cloud Administrator:
|
||||||
|
|
||||||
|
* Register / Unregister Kernel and Ramdisk Images
|
||||||
|
* Register / Unregister Machine Image (any)
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
------------
|
||||||
|
|
||||||
|
* SAML Token passing
|
||||||
|
* REST interfaces
|
||||||
|
* SOAP interfaces
|
||||||
|
|
||||||
|
Wrapping the SAML token into the API calls.
|
||||||
|
Then store the UID (fetched via backchannel) into the instance metadata, providing end-to-end auditability of ownership and responsibility, without PII.
|
||||||
|
|
||||||
|
CloudAudit APIs
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* Request formats
|
||||||
|
* Response formats
|
||||||
|
* Stateless asynchronous queries
|
||||||
|
|
||||||
|
CloudAudit queries may spawn long-running processes (similar to launching instances, etc.) They need to return a ReservationId in the same fashion, which can be returned in further queries for updates.
|
||||||
|
RBAC of CloudAudit API calls is critical, since detailed system information is a system vulnerability.
|
||||||
|
|
||||||
|
Type declarations
|
||||||
|
---------------------
|
||||||
|
* Data declarations – Volumes and Objects
|
||||||
|
* System declarations – Instances
|
||||||
|
|
||||||
|
Existing API calls to launch instances specific a single, combined “type” flag. We propose to extend this with three additional type declarations, mapping to the “Confidentiality, Integrity, Availability” classifications of FIPS 199. An example API call would look like::
|
||||||
|
|
||||||
|
RunInstances type=m1.large number=1 secgroup=default key=mykey confidentiality=low integrity=low availability=low
|
||||||
|
|
||||||
|
These additional parameters would also apply to creation of block storage volumes (along with the existing parameter of ‘size’), and creation of object storage ‘buckets’. (C.I.A. classifications on a bucket would be inherited by the keys within this bucket.)
|
||||||
|
|
||||||
|
Request Brokering
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
* Cloud Interop
|
||||||
|
* IMF Registration / PubSub
|
||||||
|
* Digital C&A
|
||||||
|
|
||||||
|
Establishing declarative semantics for individual API calls will allow the cloud environment to seamlessly proxy these API calls to external, third-party vendors – when the requested CIA levels match.
|
||||||
|
|
||||||
|
See related work within the Infrastructure 2.0 working group for more information on how the IMF Metadata specification could be utilized to manage registration of these vendors and their C&A credentials.
|
||||||
|
|
||||||
|
Dirty Cloud – Hybrid Data Centers
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
* CloudAudit bridge interfaces
|
||||||
|
* Anything in the ARP table
|
||||||
|
|
||||||
|
A hybrid cloud environment provides dedicated, potentially co-located physical hardware with a network interconnect to the project or users’ cloud virtual network.
|
||||||
|
|
||||||
|
This interconnect is typically a bridged VPN connection. Any machines that can be bridged into a hybrid environment in this fashion (at Layer 2) must implement a minimum version of the CloudAudit spec, such that they can be queried to provide a complete picture of the IT-sec runtime environment.
|
||||||
|
|
||||||
|
Network discovery protocols (ARP, CDP) can be applied in this case, and existing protocols (SNMP location data, DNS LOC records) overloaded to provide CloudAudit information.
|
||||||
|
|
||||||
|
The Details
|
||||||
|
-----------
|
||||||
|
|
||||||
|
* Preliminary Roles Definitions
|
||||||
|
* Categorization of available API calls
|
||||||
|
* SAML assertion vocabulary
|
||||||
|
|
||||||
|
System limits
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The following limits need to be defined and enforced:
|
||||||
|
|
||||||
|
* Total number of instances allowed (user / project)
|
||||||
|
* Total number of instances, per instance type (user / project)
|
||||||
|
* Total number of volumes (user / project)
|
||||||
|
* Maximum size of volume
|
||||||
|
* Cumulative size of all volumes
|
||||||
|
* Total use of object storage (GB)
|
||||||
|
* Total number of Public IPs
|
||||||
|
|
||||||
|
|
||||||
|
Further Challenges
|
||||||
|
------------------
|
||||||
|
* Prioritization of users / jobs in shared computing environments
|
||||||
|
* Incident response planning
|
||||||
|
* Limit launch of instances to specific security groups based on AMI
|
||||||
|
* Store AMIs in LDAP for added property control
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The :mod:`access` Module
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.auth.access
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`signer` Module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.auth.signer
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`users` Module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.auth.users
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`users_unittest` Module
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.users_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`access_unittest` Module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.access_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
29
docs/binaries.rst
Normal file
29
docs/binaries.rst
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Nova Binaries
|
||||||
|
===============
|
||||||
|
|
||||||
|
* nova-api
|
||||||
|
* nova-compute
|
||||||
|
* nova-manage
|
||||||
|
* nova-objectstore
|
||||||
|
* nova-volume
|
||||||
|
|
||||||
|
The configuration of these binaries relies on "flagfiles" using the google
|
||||||
|
gflags package. If present, the nova.conf file will be used as the flagfile
|
||||||
|
- otherwise, it must be specified on the command line::
|
||||||
|
|
||||||
|
$ python node_worker.py --flagfile flagfile
|
72
docs/compute.rst
Normal file
72
docs/compute.rst
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Compute Documentation
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This page contains the Compute Package documentation.
|
||||||
|
|
||||||
|
|
||||||
|
The :mod:`disk` Module
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.compute.disk
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`exception` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.compute.exception
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`model` Module
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.compute.model
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`network` Module
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.compute.network
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`node` Module
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.compute.node
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
RELATED TESTS
|
||||||
|
---------------
|
||||||
|
|
||||||
|
The :mod:`node_unittest` Module
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.node_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
202
docs/conf.py
Normal file
202
docs/conf.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# nova documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Sat May 1 15:17:47 2010.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
# 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.append(os.path.abspath('.'))
|
||||||
|
sys.path.append([os.path.abspath('../nova'),os.path.abspath('../'),os.path.abspath('../vendor')])
|
||||||
|
from nova import vendor
|
||||||
|
|
||||||
|
# -- 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', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig']
|
||||||
|
#sphinx_to_github = False
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'nova'
|
||||||
|
copyright = u'2010, Anso Labs, LLC'
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
version = '0.42'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = '0.42'
|
||||||
|
|
||||||
|
# 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 documents that shouldn't be included in the build.
|
||||||
|
#unused_docs = []
|
||||||
|
|
||||||
|
# List of directories, relative to source directory, that shouldn't be searched
|
||||||
|
# for source files.
|
||||||
|
exclude_trees = ['_build']
|
||||||
|
|
||||||
|
# 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 = ['nova.']
|
||||||
|
|
||||||
|
|
||||||
|
# -- 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 = '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']
|
||||||
|
|
||||||
|
# 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_use_modindex = 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, 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 = ''
|
||||||
|
|
||||||
|
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = ''
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'novadoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
# The paper size ('letter' or 'a4').
|
||||||
|
#latex_paper_size = 'letter'
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#latex_font_size = '10pt'
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'nova.tex', u'nova Documentation',
|
||||||
|
u'Anso Labs, LLC', '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
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#latex_preamble = ''
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_use_modindex = True
|
||||||
|
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
intersphinx_mapping = {'http://docs.python.org/': None}
|
89
docs/endpoint.rst
Normal file
89
docs/endpoint.rst
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Endpoint Documentation
|
||||||
|
======================
|
||||||
|
|
||||||
|
This page contains the Endpoint Package documentation.
|
||||||
|
|
||||||
|
The :mod:`admin` Module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.endpoint.admin
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`api` Module
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.endpoint.api
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`cloud` Module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.endpoint.cloud
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`images` Module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.endpoint.images
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
RELATED TESTS
|
||||||
|
--------------
|
||||||
|
|
||||||
|
The :mod:`api_unittest` Module
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.api_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`api_integration` Module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.api_integration
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`cloud_unittest` Module
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.cloud_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`network_unittest` Module
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.network_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
41
docs/fakes.rst
Normal file
41
docs/fakes.rst
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Nova Fakes
|
||||||
|
==========
|
||||||
|
|
||||||
|
The :mod:`fakevirt` Module
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.fakevirt
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`fakeldap` Module
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.auth.fakeldap
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`fakerabbit` Module
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.fakerabbit
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
70
docs/getting.started.rst
Normal file
70
docs/getting.started.rst
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Getting Started with Nova
|
||||||
|
=========================
|
||||||
|
|
||||||
|
|
||||||
|
GOTTA HAVE A nova.pth file added or it WONT WORK (will write setup.py file soon)
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
------------
|
||||||
|
|
||||||
|
* RabbitMQ: messaging queue, used for all communication between components
|
||||||
|
* OpenLDAP: users, groups (maybe cut)
|
||||||
|
* Tornado: scalable non blocking web server for api requests
|
||||||
|
* Twisted: just for the twisted.internet.defer package
|
||||||
|
* boto: python api for aws api
|
||||||
|
* M2Crypto: python library interface for openssl
|
||||||
|
* IPy: library for managing ip addresses
|
||||||
|
* ReDIS: Remote Dictionary Store (for fast, shared state data)
|
||||||
|
|
||||||
|
Recommended
|
||||||
|
-----------------
|
||||||
|
* euca2ools: python implementation of aws ec2-tools and ami tools
|
||||||
|
* build tornado to use C module for evented section
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
--------------
|
||||||
|
::
|
||||||
|
|
||||||
|
# ON ALL SYSTEMS
|
||||||
|
apt-get install -y python-libvirt libvirt-bin python-setuptools python-dev python-pycurl python-m2crypto python-twisted
|
||||||
|
apt-get install -y aoetools vlan
|
||||||
|
modprobe aoe
|
||||||
|
|
||||||
|
# ON THE CLOUD CONTROLLER
|
||||||
|
apt-get install -y rabbitmq-server dnsmasq
|
||||||
|
# fix ec2 metadata/userdata uri - where $IP is the IP of the cloud
|
||||||
|
iptables -t nat -A PREROUTING -s 0.0.0.0/0 -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination $IP:8773
|
||||||
|
iptables --table nat --append POSTROUTING --out-interface $PUBLICIFACE -j MASQUERADE
|
||||||
|
# setup ldap (slap.sh as root will remove ldap and reinstall it)
|
||||||
|
auth/slap.sh
|
||||||
|
/etc/init.d/rabbitmq-server start
|
||||||
|
|
||||||
|
# ON VOLUME NODE:
|
||||||
|
apt-get install -y vblade-persist
|
||||||
|
|
||||||
|
# ON THE COMPUTE NODE:
|
||||||
|
apt-get install -y kpartx kvm
|
||||||
|
|
||||||
|
# optional packages
|
||||||
|
apt-get install -y euca2ools
|
||||||
|
|
||||||
|
# Set up flagfiles with the appropriate hostnames, etc.
|
||||||
|
# start api_worker, s3_worker, node_worker, storage_worker
|
||||||
|
# Add yourself to the libvirtd group, log out, and log back in
|
||||||
|
# Make sure the user who will launch the workers has sudo privileges w/o pass (will fix later)
|
53
docs/index.rst
Normal file
53
docs/index.rst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Welcome to nova's documentation!
|
||||||
|
================================
|
||||||
|
|
||||||
|
Nova is a cloud computing fabric controller (the main part of an IaaS system) built to match the popular AWS EC2 and S3 APIs.
|
||||||
|
It is written in Python, using the Tornado and Twisted frameworks, and relies on the standard AMQP messaging protocol,
|
||||||
|
and the Redis distributed KVS.
|
||||||
|
Nova is intended to be easy to extend, and adapt. For example, it currently uses
|
||||||
|
an LDAP server for users and groups, but also includes a fake LDAP server,
|
||||||
|
that stores data in Redis. It has extensive test coverage, and uses the
|
||||||
|
Sphinx toolkit (the same as Python itself) for code and user documentation.
|
||||||
|
While Nova is currently in Beta use within several organizations, the codebase
|
||||||
|
is very much under active development - there are bugs!
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
getting.started
|
||||||
|
architecture
|
||||||
|
network
|
||||||
|
storage
|
||||||
|
auth
|
||||||
|
compute
|
||||||
|
endpoint
|
||||||
|
nova
|
||||||
|
fakes
|
||||||
|
binaries
|
||||||
|
todo
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
|
|
32
docs/modules.rst
Normal file
32
docs/modules.rst
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Nova Documentation
|
||||||
|
==================
|
||||||
|
|
||||||
|
This page contains the Nova Modules documentation.
|
||||||
|
|
||||||
|
Modules:
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
auth
|
||||||
|
compute
|
||||||
|
endpoint
|
||||||
|
fakes
|
||||||
|
nova
|
||||||
|
volume
|
86
docs/network.rst
Normal file
86
docs/network.rst
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
nova Networking
|
||||||
|
================
|
||||||
|
|
||||||
|
The nova networking components manage private networks, public IP addressing, VPN connectivity, and firewall rules.
|
||||||
|
|
||||||
|
Components
|
||||||
|
----------
|
||||||
|
There are several key components:
|
||||||
|
|
||||||
|
* NetworkController (Manages address and vlan allocation)
|
||||||
|
* RoutingNode (NATs public IPs to private IPs, and enforces firewall rules)
|
||||||
|
* AddressingNode (runs DHCP services for private networks)
|
||||||
|
* BridgingNode (a subclass of the basic nova ComputeNode)
|
||||||
|
* TunnelingNode (provides VPN connectivity)
|
||||||
|
|
||||||
|
Component Diagram
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Overview::
|
||||||
|
|
||||||
|
(PUBLIC INTERNET)
|
||||||
|
| \
|
||||||
|
/ \ / \
|
||||||
|
[RoutingNode] ... [RN] [TunnelingNode] ... [TN]
|
||||||
|
| \ / | |
|
||||||
|
| < AMQP > | |
|
||||||
|
[AddressingNode]-- (VLAN) ... | (VLAN)... (VLAN) --- [AddressingNode]
|
||||||
|
\ | \ /
|
||||||
|
/ \ / \ / \ / \
|
||||||
|
[BridgingNode] ... [BridgingNode]
|
||||||
|
|
||||||
|
|
||||||
|
[NetworkController] ... [NetworkController]
|
||||||
|
\ /
|
||||||
|
< AMQP >
|
||||||
|
|
|
||||||
|
/ \
|
||||||
|
[CloudController]...[CloudController]
|
||||||
|
|
||||||
|
While this diagram may not make this entirely clear, nodes and controllers communicate exclusively across the message bus (AMQP, currently).
|
||||||
|
|
||||||
|
State Model
|
||||||
|
-----------
|
||||||
|
Network State consists of the following facts:
|
||||||
|
|
||||||
|
* VLAN assignment (to a project)
|
||||||
|
* Private Subnet assignment (to a security group) in a VLAN
|
||||||
|
* Private IP assignments (to running instances)
|
||||||
|
* Public IP allocations (to a project)
|
||||||
|
* Public IP associations (to a private IP / running instance)
|
||||||
|
|
||||||
|
While copies of this state exist in many places (expressed in IPTables rule chains, DHCP hosts files, etc), the controllers rely only on the distributed "fact engine" for state, queried over RPC (currently AMQP). The NetworkController inserts most records into this datastore (allocating addresses, etc) - however, individual nodes update state e.g. when running instances crash.
|
||||||
|
|
||||||
|
The Public Traffic Path
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Public Traffic::
|
||||||
|
|
||||||
|
(PUBLIC INTERNET)
|
||||||
|
|
|
||||||
|
<NAT> <-- [RoutingNode]
|
||||||
|
|
|
||||||
|
[AddressingNode] --> |
|
||||||
|
( VLAN )
|
||||||
|
| <-- [BridgingNode]
|
||||||
|
|
|
||||||
|
<RUNNING INSTANCE>
|
||||||
|
|
||||||
|
The RoutingNode is currently implemented using IPTables rules, which implement both NATing of public IP addresses, and the appropriate firewall chains. We are also looking at using Netomata / Clusto to manage NATting within a switch or router, and/or to manage firewall rules within a hardware firewall appliance.
|
||||||
|
|
||||||
|
Similarly, the AddressingNode currently manages running DNSMasq instances for DHCP services. However, we could run an internal DHCP server (using Scapy ala Clusto), or even switch to static addressing by inserting the private address into the disk image the same way we insert the SSH keys. (See compute for more details).
|
89
docs/nova.rst
Normal file
89
docs/nova.rst
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
NOVA Libraries
|
||||||
|
===============
|
||||||
|
|
||||||
|
The :mod:`crypto` Module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.crypto
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`adminclient` Module
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.adminclient
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`datastore` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.datastore
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`exception` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.exception
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`flags` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.flags
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`rpc` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.rpc
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`server` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.server
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`test` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.test
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`utils` Module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.utils
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
64
docs/objectstore.rst
Normal file
64
docs/objectstore.rst
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Objectstore Documentation
|
||||||
|
=========================
|
||||||
|
|
||||||
|
This page contains the Objectstore Package documentation.
|
||||||
|
|
||||||
|
|
||||||
|
The :mod:`bucket` Module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.objectstore.bucket
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`handler` Module
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.objectstore.handler
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`image` Module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.objectstore.image
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`stored` Module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.objectstore.stored
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
RELATED TESTS
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The :mod:`objectstore_unittest` Module
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.objectstore_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
27
docs/packages.rst
Normal file
27
docs/packages.rst
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
nova Packages & Dependencies
|
||||||
|
============================
|
||||||
|
|
||||||
|
Nova is being built on Ubuntu Lucid.
|
||||||
|
|
||||||
|
The following packages are required:
|
||||||
|
|
||||||
|
apt-get install python-ipy, python-libvirt, python-boto, python-pycurl, python-twisted, python-daemon, python-redis, python-carrot, python-lockfile
|
||||||
|
|
||||||
|
In addition you need to install python:
|
||||||
|
|
||||||
|
* python-gflags - http://code.google.com/p/python-gflags/
|
29
docs/storage.rst
Normal file
29
docs/storage.rst
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Storage in the Nova Cloud
|
||||||
|
=========================
|
||||||
|
|
||||||
|
There are three primary classes of storage in a nova cloud environment:
|
||||||
|
|
||||||
|
* Ephemeral Storage (local disk within an instance)
|
||||||
|
* Volume Storage (network-attached FS)
|
||||||
|
* Object Storage (redundant KVS with locality and MR)
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
volume
|
||||||
|
objectstore
|
43
docs/volume.rst
Normal file
43
docs/volume.rst
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
..
|
||||||
|
Copyright [2010] [Anso Labs, LLC]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Volume Documentation
|
||||||
|
====================
|
||||||
|
|
||||||
|
Nova uses ata-over-ethernet (AoE) to export storage volumes from multiple storage nodes. These AoE exports are attached (using libvirt) directly to running instances.
|
||||||
|
|
||||||
|
Nova volumes are exported over the primary system VLAN (usually VLAN 1), and not over individual VLANs.
|
||||||
|
|
||||||
|
AoE exports are numbered according to a "shelf and blade" syntax. In order to avoid collisions, we currently perform an AoE-discover of existing exports, and then grab the next unused number. (This obviously has race condition problems, and should be replaced by allocating a shelf-id to each storage node.)
|
||||||
|
|
||||||
|
The underlying volumes are LVM logical volumes, created on demand within a single large volume group.
|
||||||
|
|
||||||
|
|
||||||
|
The :mod:`storage` Module
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.volume.storage
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
The :mod:`storage_unittest` Module
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: nova.tests.storage_unittest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
113
nova/adminclient.py
Normal file
113
nova/adminclient.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Nova User API client library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import boto
|
||||||
|
from boto.ec2.regioninfo import RegionInfo
|
||||||
|
import base64
|
||||||
|
|
||||||
|
class UserInfo(object):
|
||||||
|
""" Information about a Nova user
|
||||||
|
fields include:
|
||||||
|
username
|
||||||
|
accesskey
|
||||||
|
secretkey
|
||||||
|
|
||||||
|
and an optional field containing a zip with X509 cert & rc
|
||||||
|
file
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None, username=None, endpoint=None):
|
||||||
|
self.connection = connection
|
||||||
|
self.username = username
|
||||||
|
self.endpoint = endpoint
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'UserInfo:%s' % self.username
|
||||||
|
|
||||||
|
def startElement(self, name, attrs, connection):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def endElement(self, name, value, connection):
|
||||||
|
if name == 'username':
|
||||||
|
self.username = str(value)
|
||||||
|
elif name == 'file':
|
||||||
|
self.file = base64.b64decode(str(value))
|
||||||
|
elif name == 'accesskey':
|
||||||
|
self.accesskey = str(value)
|
||||||
|
elif name == 'secretkey':
|
||||||
|
self.secretkey = str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class NovaAdminClient(object):
|
||||||
|
def __init__(self, clc_ip='127.0.0.1', region='nova', access_key='admin',
|
||||||
|
secret_key='admin', **kwargs):
|
||||||
|
self.clc_ip = clc_ip
|
||||||
|
self.region = region
|
||||||
|
self.access = access_key
|
||||||
|
self.secret = secret_key
|
||||||
|
self.apiconn = boto.connect_ec2(aws_access_key_id=access_key,
|
||||||
|
aws_secret_access_key=secret_key,
|
||||||
|
is_secure=False,
|
||||||
|
region=RegionInfo(None, region, clc_ip),
|
||||||
|
port=8773,
|
||||||
|
path='/services/Admin',
|
||||||
|
**kwargs)
|
||||||
|
self.apiconn.APIVersion = 'nova'
|
||||||
|
|
||||||
|
def connection_for(self, username, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns a boto ec2 connection for the given username.
|
||||||
|
"""
|
||||||
|
user = self.get_user(username)
|
||||||
|
return boto.connect_ec2(
|
||||||
|
aws_access_key_id=user.accesskey,
|
||||||
|
aws_secret_access_key=user.secretkey,
|
||||||
|
is_secure=False,
|
||||||
|
region=RegionInfo(None, self.region, self.clc_ip),
|
||||||
|
port=8773,
|
||||||
|
path='/services/Cloud',
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_users(self):
|
||||||
|
""" grabs the list of all users """
|
||||||
|
return self.apiconn.get_list('DescribeUsers', {}, (['item', UserInfo]))
|
||||||
|
|
||||||
|
def get_user(self, name):
|
||||||
|
""" grab a single user by name """
|
||||||
|
user = self.apiconn.get_object('DescribeUser', {'Name': name}, UserInfo)
|
||||||
|
|
||||||
|
if user.username != None:
|
||||||
|
return user
|
||||||
|
|
||||||
|
def has_user(self, username):
|
||||||
|
""" determine if user exists """
|
||||||
|
return self.get_user(username) != None
|
||||||
|
|
||||||
|
def create_user(self, username):
|
||||||
|
""" creates a new user, returning the userinfo object with access/secret """
|
||||||
|
return self.apiconn.get_object('RegisterUser', {'Name': username}, UserInfo)
|
||||||
|
|
||||||
|
def delete_user(self, username):
|
||||||
|
""" deletes a user """
|
||||||
|
return self.apiconn.get_object('DeregisterUser', {'Name': username}, UserInfo)
|
||||||
|
|
||||||
|
def get_zip(self, username):
|
||||||
|
""" returns the content of a zip file containing novarc and access credentials. """
|
||||||
|
return self.apiconn.get_object('GenerateX509ForUser', {'Name': username}, UserInfo).file
|
||||||
|
|
25
nova/auth/__init__.py
Normal file
25
nova/auth/__init__.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
:mod:`nova.auth` -- Authentication and Access Control
|
||||||
|
=====================================================
|
||||||
|
|
||||||
|
.. automodule:: nova.auth
|
||||||
|
:platform: Unix
|
||||||
|
:synopsis: User-and-Project based RBAC using LDAP, SAML.
|
||||||
|
.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
|
||||||
|
.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
|
||||||
|
.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
|
||||||
|
"""
|
69
nova/auth/access.py
Normal file
69
nova/auth/access.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Simple base set of RBAC rules which map API endpoints to LDAP groups.
|
||||||
|
For testing accounts, users will always have PM privileges.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# This is logically a RuleSet or some such.
|
||||||
|
|
||||||
|
def allow_describe_images(user, project, target_object):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_describe_instances(user, project, target_object):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_describe_addresses(user, project, target_object):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_run_instances(user, project, target_object):
|
||||||
|
# target_object is a reservation, not an instance
|
||||||
|
# it needs to include count, type, image, etc.
|
||||||
|
|
||||||
|
# First, is the project allowed to use this image
|
||||||
|
|
||||||
|
# Second, is this user allowed to launch within this project
|
||||||
|
|
||||||
|
# Third, is the count or type within project quota
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_terminate_instances(user, project, target_object):
|
||||||
|
# In a project, the PMs and Sysadmins can terminate
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_get_console_output(user, project, target_object):
|
||||||
|
# If the user launched the instance,
|
||||||
|
# Or is a sysadmin in the project,
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_allocate_address(user, project, target_object):
|
||||||
|
# There's no security concern in allocation,
|
||||||
|
# but it can get expensive. Limit to PM and NE.
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_associate_address(user, project, target_object):
|
||||||
|
# project NE only
|
||||||
|
# In future, will perform a CloudAudit scan first
|
||||||
|
# (Pass / Fail gate)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def allow_register(user, project, target_object):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_allowed(action, user, project, target_object):
|
||||||
|
return globals()['allow_%s' % action](user, project, target_object)
|
||||||
|
|
81
nova/auth/fakeldap.py
Normal file
81
nova/auth/fakeldap.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Fake LDAP server for test harnesses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from nova import datastore
|
||||||
|
|
||||||
|
SCOPE_SUBTREE = 1
|
||||||
|
|
||||||
|
|
||||||
|
class NO_SUCH_OBJECT(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(uri):
|
||||||
|
return FakeLDAP(uri)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeLDAP(object):
|
||||||
|
def __init__(self, _uri):
|
||||||
|
self.keeper = datastore.Keeper('fakeldap')
|
||||||
|
if self.keeper['objects'] is None:
|
||||||
|
self.keeper['objects'] = {}
|
||||||
|
|
||||||
|
def simple_bind_s(self, dn, password):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unbind_s(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def search_s(self, dn, scope, query=None, fields=None):
|
||||||
|
logging.debug("searching for %s" % dn)
|
||||||
|
filtered = {}
|
||||||
|
d = self.keeper['objects'] or {}
|
||||||
|
for cn, attrs in d.iteritems():
|
||||||
|
if cn[-len(dn):] == dn:
|
||||||
|
filtered[cn] = attrs
|
||||||
|
if query:
|
||||||
|
k,v = query[1:-1].split('=')
|
||||||
|
objects = {}
|
||||||
|
for cn, attrs in filtered.iteritems():
|
||||||
|
if attrs.has_key(k) and (v in attrs[k] or
|
||||||
|
v == attrs[k]):
|
||||||
|
objects[cn] = attrs
|
||||||
|
if objects == {}:
|
||||||
|
raise NO_SUCH_OBJECT()
|
||||||
|
return objects.items()
|
||||||
|
|
||||||
|
def add_s(self, cn, attr):
|
||||||
|
logging.debug("adding %s" % cn)
|
||||||
|
stored = {}
|
||||||
|
for k, v in attr:
|
||||||
|
if type(v) is list:
|
||||||
|
stored[k] = v
|
||||||
|
else:
|
||||||
|
stored[k] = [v]
|
||||||
|
d = self.keeper['objects']
|
||||||
|
d[cn] = stored
|
||||||
|
self.keeper['objects'] = d
|
||||||
|
|
||||||
|
def delete_s(self, cn):
|
||||||
|
logging.debug("creating for %s" % cn)
|
||||||
|
d = self.keeper['objects'] or {}
|
||||||
|
del d[cn]
|
||||||
|
self.keeper['objects'] = d
|
26
nova/auth/novarc.template
Normal file
26
nova/auth/novarc.template
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
NOVA_KEY_DIR=$(pushd $(dirname $BASH_SOURCE)>/dev/null; pwd; popd>/dev/null)
|
||||||
|
export EC2_ACCESS_KEY="%(access)s"
|
||||||
|
export EC2_SECRET_KEY="%(secret)s"
|
||||||
|
export EC2_URL="%(ec2)s"
|
||||||
|
export S3_URL="%(s3)s"
|
||||||
|
export EC2_USER_ID=42 # nova does not use user id, but bundling requires it
|
||||||
|
export EC2_PRIVATE_KEY=${NOVA_KEY_DIR}/%(key)s
|
||||||
|
export EC2_CERT=${NOVA_KEY_DIR}/%(cert)s
|
||||||
|
export NOVA_CERT=${NOVA_KEY_DIR}/%(nova)s
|
||||||
|
export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set
|
||||||
|
alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
|
||||||
|
alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"
|
60
nova/auth/rbac.ldif
Normal file
60
nova/auth/rbac.ldif
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# LDIF fragment to create group branch under root
|
||||||
|
|
||||||
|
#dn: ou=Groups,dc=example,dc=com
|
||||||
|
#objectclass:organizationalunit
|
||||||
|
#ou: groups
|
||||||
|
#description: generic groups branch
|
||||||
|
|
||||||
|
# create the itpeople entry
|
||||||
|
|
||||||
|
dn: cn=sysadmins,ou=Groups,dc=example,dc=com
|
||||||
|
objectclass: groupofnames
|
||||||
|
cn: itpeople
|
||||||
|
description: IT admin group
|
||||||
|
# add the group members all of which are
|
||||||
|
# assumed to exist under Users
|
||||||
|
#member: cn=micky mouse,ou=people,dc=example,dc=com
|
||||||
|
member: cn=admin,ou=Users,dc=example,dc=com
|
||||||
|
|
||||||
|
dn: cn=netadmins,ou=Groups,dc=example,dc=com
|
||||||
|
objectclass: groupofnames
|
||||||
|
cn: netadmins
|
||||||
|
description: Network admin group
|
||||||
|
member: cn=admin,ou=Users,dc=example,dc=com
|
||||||
|
|
||||||
|
dn: cn=cloudadmins,ou=Groups,dc=example,dc=com
|
||||||
|
objectclass: groupofnames
|
||||||
|
cn: cloudadmins
|
||||||
|
description: Cloud admin group
|
||||||
|
member: cn=admin,ou=Users,dc=example,dc=com
|
||||||
|
|
||||||
|
dn: cn=itsec,ou=Groups,dc=example,dc=com
|
||||||
|
objectclass: groupofnames
|
||||||
|
cn: itsec
|
||||||
|
description: IT security users group
|
||||||
|
member: cn=admin,ou=Users,dc=example,dc=com
|
||||||
|
|
||||||
|
# Example Project Group to demonstrate members
|
||||||
|
# and project members
|
||||||
|
|
||||||
|
dn: cn=myproject,ou=Groups,dc=example,dc=com
|
||||||
|
objectclass: groupofnames
|
||||||
|
objectclass: novaProject
|
||||||
|
cn: myproject
|
||||||
|
description: My Project Group
|
||||||
|
member: cn=admin,ou=Users,dc=example,dc=com
|
||||||
|
projectManager: cn=admin,ou=Users,dc=example,dc=com
|
127
nova/auth/signer.py
Normal file
127
nova/auth/signer.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# PORTIONS OF THIS FILE ARE FROM:
|
||||||
|
# http://code.google.com/p/boto
|
||||||
|
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Utility class for parsing signed AMI manifests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import urllib
|
||||||
|
import base64
|
||||||
|
from nova.exception import Error
|
||||||
|
|
||||||
|
_log = logging.getLogger('signer')
|
||||||
|
logging.getLogger('signer').setLevel(logging.WARN)
|
||||||
|
|
||||||
|
class Signer(object):
|
||||||
|
""" hacked up code from boto/connection.py """
|
||||||
|
|
||||||
|
def __init__(self, secret_key):
|
||||||
|
self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
|
||||||
|
if hashlib.sha256:
|
||||||
|
self.hmac_256 = hmac.new(secret_key, digestmod=hashlib.sha256)
|
||||||
|
|
||||||
|
def generate(self, params, verb, server_string, path):
|
||||||
|
if params['SignatureVersion'] == '0':
|
||||||
|
t = self._calc_signature_0(params)
|
||||||
|
elif params['SignatureVersion'] == '1':
|
||||||
|
t = self._calc_signature_1(params)
|
||||||
|
elif params['SignatureVersion'] == '2':
|
||||||
|
t = self._calc_signature_2(params, verb, server_string, path)
|
||||||
|
else:
|
||||||
|
raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
|
||||||
|
return t
|
||||||
|
|
||||||
|
def _get_utf8_value(self, value):
|
||||||
|
if not isinstance(value, str) and not isinstance(value, unicode):
|
||||||
|
value = str(value)
|
||||||
|
if isinstance(value, unicode):
|
||||||
|
return value.encode('utf-8')
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _calc_signature_0(self, params):
|
||||||
|
s = params['Action'] + params['Timestamp']
|
||||||
|
self.hmac.update(s)
|
||||||
|
keys = params.keys()
|
||||||
|
keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
|
||||||
|
pairs = []
|
||||||
|
for key in keys:
|
||||||
|
val = self._get_utf8_value(params[key])
|
||||||
|
pairs.append(key + '=' + urllib.quote(val))
|
||||||
|
return base64.b64encode(self.hmac.digest())
|
||||||
|
|
||||||
|
def _calc_signature_1(self, params):
|
||||||
|
keys = params.keys()
|
||||||
|
keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
|
||||||
|
pairs = []
|
||||||
|
for key in keys:
|
||||||
|
self.hmac.update(key)
|
||||||
|
val = self._get_utf8_value(params[key])
|
||||||
|
self.hmac.update(val)
|
||||||
|
pairs.append(key + '=' + urllib.quote(val))
|
||||||
|
return base64.b64encode(self.hmac.digest())
|
||||||
|
|
||||||
|
def _calc_signature_2(self, params, verb, server_string, path):
|
||||||
|
_log.debug('using _calc_signature_2')
|
||||||
|
string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
|
||||||
|
if self.hmac_256:
|
||||||
|
hmac = self.hmac_256
|
||||||
|
params['SignatureMethod'] = 'HmacSHA256'
|
||||||
|
else:
|
||||||
|
hmac = self.hmac
|
||||||
|
params['SignatureMethod'] = 'HmacSHA1'
|
||||||
|
keys = params.keys()
|
||||||
|
keys.sort()
|
||||||
|
pairs = []
|
||||||
|
for key in keys:
|
||||||
|
val = self._get_utf8_value(params[key])
|
||||||
|
pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
|
||||||
|
qs = '&'.join(pairs)
|
||||||
|
_log.debug('query string: %s' % qs)
|
||||||
|
string_to_sign += qs
|
||||||
|
_log.debug('string_to_sign: %s' % string_to_sign)
|
||||||
|
hmac.update(string_to_sign)
|
||||||
|
b64 = base64.b64encode(hmac.digest())
|
||||||
|
_log.debug('len(b64)=%d' % len(b64))
|
||||||
|
_log.debug('base64 encoded digest: %s' % b64)
|
||||||
|
return b64
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print Signer('foo').generate({"SignatureMethod": 'HmacSHA256', 'SignatureVersion': '2'}, "get", "server", "/foo")
|
226
nova/auth/slap.sh
Executable file
226
nova/auth/slap.sh
Executable file
@ -0,0 +1,226 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# LDAP INSTALL SCRIPT - SHOULD BE IDEMPOTENT, but it SCRUBS all USERS
|
||||||
|
|
||||||
|
apt-get install -y slapd ldap-utils python-ldap
|
||||||
|
|
||||||
|
cat >/etc/ldap/schema/openssh-lpk_openldap.schema <<LPK_SCHEMA_EOF
|
||||||
|
#
|
||||||
|
# LDAP Public Key Patch schema for use with openssh-ldappubkey
|
||||||
|
# Author: Eric AUGE <eau@phear.org>
|
||||||
|
#
|
||||||
|
# Based on the proposal of : Mark Ruijter
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# octetString SYNTAX
|
||||||
|
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
|
||||||
|
DESC 'MANDATORY: OpenSSH Public key'
|
||||||
|
EQUALITY octetStringMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
|
||||||
|
|
||||||
|
# printableString SYNTAX yes|no
|
||||||
|
objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
|
||||||
|
DESC 'MANDATORY: OpenSSH LPK objectclass'
|
||||||
|
MAY ( sshPublicKey $ uid )
|
||||||
|
)
|
||||||
|
LPK_SCHEMA_EOF
|
||||||
|
|
||||||
|
cat >/etc/ldap/schema/nova.schema <<NOVA_SCHEMA_EOF
|
||||||
|
#
|
||||||
|
# Person object for Nova
|
||||||
|
# inetorgperson with extra attributes
|
||||||
|
# Author: Vishvananda Ishaya <vishvananda@yahoo.com>
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
# using internet experimental oid arc as per BP64 3.1
|
||||||
|
objectidentifier novaSchema 1.3.6.1.3.1.666.666
|
||||||
|
objectidentifier novaAttrs novaSchema:3
|
||||||
|
objectidentifier novaOCs novaSchema:4
|
||||||
|
|
||||||
|
attributetype (
|
||||||
|
novaAttrs:1
|
||||||
|
NAME 'accessKey'
|
||||||
|
DESC 'Key for accessing data'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||||
|
SINGLE-VALUE
|
||||||
|
)
|
||||||
|
|
||||||
|
attributetype (
|
||||||
|
novaAttrs:2
|
||||||
|
NAME 'secretKey'
|
||||||
|
DESC 'Secret key'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||||
|
SINGLE-VALUE
|
||||||
|
)
|
||||||
|
|
||||||
|
attributetype (
|
||||||
|
novaAttrs:3
|
||||||
|
NAME 'keyFingerprint'
|
||||||
|
DESC 'Fingerprint of private key'
|
||||||
|
EQUALITY caseIgnoreMatch
|
||||||
|
SUBSTR caseIgnoreSubstringsMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||||
|
SINGLE-VALUE
|
||||||
|
)
|
||||||
|
|
||||||
|
attributetype (
|
||||||
|
novaAttrs:4
|
||||||
|
NAME 'isAdmin'
|
||||||
|
DESC 'Is user an administrator?'
|
||||||
|
EQUALITY booleanMatch
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
|
||||||
|
SINGLE-VALUE
|
||||||
|
)
|
||||||
|
|
||||||
|
attributetype (
|
||||||
|
novaAttrs:5
|
||||||
|
NAME 'projectManager'
|
||||||
|
DESC 'Project Managers of a project'
|
||||||
|
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
|
||||||
|
)
|
||||||
|
|
||||||
|
objectClass (
|
||||||
|
novaOCs:1
|
||||||
|
NAME 'novaUser'
|
||||||
|
DESC 'access and secret keys'
|
||||||
|
AUXILIARY
|
||||||
|
MUST ( uid )
|
||||||
|
MAY ( accessKey $ secretKey $ isAdmin )
|
||||||
|
)
|
||||||
|
|
||||||
|
objectClass (
|
||||||
|
novaOCs:2
|
||||||
|
NAME 'novaKeyPair'
|
||||||
|
DESC 'Key pair for User'
|
||||||
|
SUP top
|
||||||
|
STRUCTURAL
|
||||||
|
MUST ( cn $ sshPublicKey $ keyFingerprint )
|
||||||
|
)
|
||||||
|
|
||||||
|
objectClass (
|
||||||
|
novaOCs:3
|
||||||
|
NAME 'novaProject'
|
||||||
|
DESC 'Container for project'
|
||||||
|
SUP groupofnames
|
||||||
|
STRUCTURAL
|
||||||
|
MUST ( cn $ projectManager )
|
||||||
|
)
|
||||||
|
|
||||||
|
NOVA_SCHEMA_EOF
|
||||||
|
|
||||||
|
mv /etc/ldap/slapd.conf /etc/ldap/slapd.conf.orig
|
||||||
|
cat >/etc/ldap/slapd.conf <<SLAPD_CONF_EOF
|
||||||
|
# slapd.conf - Configuration file for LDAP SLAPD
|
||||||
|
##########
|
||||||
|
# Basics #
|
||||||
|
##########
|
||||||
|
include /etc/ldap/schema/core.schema
|
||||||
|
include /etc/ldap/schema/cosine.schema
|
||||||
|
include /etc/ldap/schema/inetorgperson.schema
|
||||||
|
include /etc/ldap/schema/openssh-lpk_openldap.schema
|
||||||
|
include /etc/ldap/schema/nova.schema
|
||||||
|
pidfile /var/run/slapd/slapd.pid
|
||||||
|
argsfile /var/run/slapd/slapd.args
|
||||||
|
loglevel none
|
||||||
|
modulepath /usr/lib/ldap
|
||||||
|
# modulepath /usr/local/libexec/openldap
|
||||||
|
moduleload back_hdb
|
||||||
|
##########################
|
||||||
|
# Database Configuration #
|
||||||
|
##########################
|
||||||
|
database hdb
|
||||||
|
suffix "dc=example,dc=com"
|
||||||
|
rootdn "cn=Manager,dc=example,dc=com"
|
||||||
|
rootpw changeme
|
||||||
|
directory /var/lib/ldap
|
||||||
|
# directory /usr/local/var/openldap-data
|
||||||
|
index objectClass,cn eq
|
||||||
|
########
|
||||||
|
# ACLs #
|
||||||
|
########
|
||||||
|
access to attrs=userPassword
|
||||||
|
by anonymous auth
|
||||||
|
by self write
|
||||||
|
by * none
|
||||||
|
access to *
|
||||||
|
by self write
|
||||||
|
by * none
|
||||||
|
SLAPD_CONF_EOF
|
||||||
|
|
||||||
|
mv /etc/ldap/ldap.conf /etc/ldap/ldap.conf.orig
|
||||||
|
|
||||||
|
cat >/etc/ldap/ldap.conf <<LDAP_CONF_EOF
|
||||||
|
# LDAP Client Settings
|
||||||
|
URI ldap://localhost
|
||||||
|
BASE dc=example,dc=com
|
||||||
|
BINDDN cn=Manager,dc=example,dc=com
|
||||||
|
SIZELIMIT 0
|
||||||
|
TIMELIMIT 0
|
||||||
|
LDAP_CONF_EOF
|
||||||
|
|
||||||
|
cat >/etc/ldap/base.ldif <<BASE_LDIF_EOF
|
||||||
|
# This is the root of the directory tree
|
||||||
|
dn: dc=example,dc=com
|
||||||
|
description: Example.Com, your trusted non-existent corporation.
|
||||||
|
dc: example
|
||||||
|
o: Example.Com
|
||||||
|
objectClass: top
|
||||||
|
objectClass: dcObject
|
||||||
|
objectClass: organization
|
||||||
|
|
||||||
|
# Subtree for users
|
||||||
|
dn: ou=Users,dc=example,dc=com
|
||||||
|
ou: Users
|
||||||
|
description: Users
|
||||||
|
objectClass: organizationalUnit
|
||||||
|
|
||||||
|
# Subtree for groups
|
||||||
|
dn: ou=Groups,dc=example,dc=com
|
||||||
|
ou: Groups
|
||||||
|
description: Groups
|
||||||
|
objectClass: organizationalUnit
|
||||||
|
|
||||||
|
# Subtree for system accounts
|
||||||
|
dn: ou=System,dc=example,dc=com
|
||||||
|
ou: System
|
||||||
|
description: Special accounts used by software applications.
|
||||||
|
objectClass: organizationalUnit
|
||||||
|
|
||||||
|
# Special Account for Authentication:
|
||||||
|
dn: uid=authenticate,ou=System,dc=example,dc=com
|
||||||
|
uid: authenticate
|
||||||
|
ou: System
|
||||||
|
description: Special account for authenticating users
|
||||||
|
userPassword: {MD5}TLnIqASP0CKUR3/LGkEZGg==
|
||||||
|
objectClass: account
|
||||||
|
objectClass: simpleSecurityObject
|
||||||
|
BASE_LDIF_EOF
|
||||||
|
|
||||||
|
/etc/init.d/slapd stop
|
||||||
|
rm -rf /var/lib/ldap/*
|
||||||
|
rm -rf /etc/ldap/slapd.d/*
|
||||||
|
slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d
|
||||||
|
cp /usr/share/slapd/DB_CONFIG /var/lib/ldap/DB_CONFIG
|
||||||
|
slapadd -v -l /etc/ldap/base.ldif
|
||||||
|
chown -R openldap:openldap /etc/ldap/slapd.d
|
||||||
|
chown -R openldap:openldap /var/lib/ldap
|
||||||
|
/etc/init.d/slapd start
|
454
nova/auth/users.py
Executable file
454
nova/auth/users.py
Executable file
@ -0,0 +1,454 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Nova users and user management, including RBAC hooks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import uuid
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ldap
|
||||||
|
except Exception, e:
|
||||||
|
import fakeldap as ldap
|
||||||
|
|
||||||
|
import fakeldap
|
||||||
|
from nova import datastore
|
||||||
|
|
||||||
|
# TODO(termie): clean up these imports
|
||||||
|
import signer
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import crypto
|
||||||
|
from nova import utils
|
||||||
|
import access as simplerbac
|
||||||
|
|
||||||
|
from nova import objectstore # for flags
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap server')
|
||||||
|
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
|
||||||
|
flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user')
|
||||||
|
flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
|
||||||
|
flags.DEFINE_string('ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
|
||||||
|
|
||||||
|
flags.DEFINE_string('ldap_sysadmin',
|
||||||
|
'cn=sysadmins,ou=Groups,dc=example,dc=com', 'OU for Sysadmins')
|
||||||
|
flags.DEFINE_string('ldap_netadmin',
|
||||||
|
'cn=netadmins,ou=Groups,dc=example,dc=com', 'OU for NetAdmins')
|
||||||
|
flags.DEFINE_string('ldap_cloudadmin',
|
||||||
|
'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'OU for Cloud Admins')
|
||||||
|
flags.DEFINE_string('ldap_itsec',
|
||||||
|
'cn=itsec,ou=Groups,dc=example,dc=com', 'OU for ItSec')
|
||||||
|
|
||||||
|
flags.DEFINE_string('credentials_template',
|
||||||
|
utils.abspath('auth/novarc.template'),
|
||||||
|
'Template for creating users rc file')
|
||||||
|
flags.DEFINE_string('credential_key_file', 'pk.pem',
|
||||||
|
'Filename of private key in credentials zip')
|
||||||
|
flags.DEFINE_string('credential_cert_file', 'cert.pem',
|
||||||
|
'Filename of certificate in credentials zip')
|
||||||
|
flags.DEFINE_string('credential_rc_file', 'novarc',
|
||||||
|
'Filename of rc in credentials zip')
|
||||||
|
|
||||||
|
_log = logging.getLogger('auth')
|
||||||
|
_log.setLevel(logging.WARN)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class UserError(exception.ApiError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InvalidKeyPair(exception.ApiError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class User(object):
|
||||||
|
def __init__(self, id, name, access, secret, admin):
|
||||||
|
self.manager = UserManager.instance()
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.access = access
|
||||||
|
self.secret = secret
|
||||||
|
self.admin = admin
|
||||||
|
self.keeper = datastore.Keeper(prefix="user")
|
||||||
|
|
||||||
|
|
||||||
|
def is_admin(self):
|
||||||
|
return self.admin
|
||||||
|
|
||||||
|
def has_role(self, role_type):
|
||||||
|
return self.manager.has_role(self.id, role_type)
|
||||||
|
|
||||||
|
def is_authorized(self, owner_id, action=None):
|
||||||
|
if self.is_admin() or owner_id == self.id:
|
||||||
|
return True
|
||||||
|
if action == None:
|
||||||
|
return False
|
||||||
|
project = None #(Fixme)
|
||||||
|
target_object = None # (Fixme, should be passed in)
|
||||||
|
return simplerbac.is_allowed(action, self, project, target_object)
|
||||||
|
|
||||||
|
def get_credentials(self):
|
||||||
|
rc = self.generate_rc()
|
||||||
|
private_key, signed_cert = self.generate_x509_cert()
|
||||||
|
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
zf = os.path.join(tmpdir, "temp.zip")
|
||||||
|
zippy = zipfile.ZipFile(zf, 'w')
|
||||||
|
zippy.writestr(FLAGS.credential_rc_file, rc)
|
||||||
|
zippy.writestr(FLAGS.credential_key_file, private_key)
|
||||||
|
zippy.writestr(FLAGS.credential_cert_file, signed_cert)
|
||||||
|
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(self.id))
|
||||||
|
zippy.close()
|
||||||
|
with open(zf, 'rb') as f:
|
||||||
|
buffer = f.read()
|
||||||
|
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
|
||||||
|
def generate_rc(self):
|
||||||
|
rc = open(FLAGS.credentials_template).read()
|
||||||
|
rc = rc % { 'access': self.access,
|
||||||
|
'secret': self.secret,
|
||||||
|
'ec2': FLAGS.ec2_url,
|
||||||
|
's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
|
||||||
|
'nova': FLAGS.ca_file,
|
||||||
|
'cert': FLAGS.credential_cert_file,
|
||||||
|
'key': FLAGS.credential_key_file,
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def generate_key_pair(self, name):
|
||||||
|
return self.manager.generate_key_pair(self.id, name)
|
||||||
|
|
||||||
|
def generate_x509_cert(self):
|
||||||
|
return self.manager.generate_x509_cert(self.id)
|
||||||
|
|
||||||
|
def create_key_pair(self, name, public_key, fingerprint):
|
||||||
|
return self.manager.create_key_pair(self.id,
|
||||||
|
name,
|
||||||
|
public_key,
|
||||||
|
fingerprint)
|
||||||
|
|
||||||
|
def get_key_pair(self, name):
|
||||||
|
return self.manager.get_key_pair(self.id, name)
|
||||||
|
|
||||||
|
def delete_key_pair(self, name):
|
||||||
|
return self.manager.delete_key_pair(self.id, name)
|
||||||
|
|
||||||
|
def get_key_pairs(self):
|
||||||
|
return self.manager.get_key_pairs(self.id)
|
||||||
|
|
||||||
|
class KeyPair(object):
|
||||||
|
def __init__(self, name, owner, public_key, fingerprint):
|
||||||
|
self.manager = UserManager.instance()
|
||||||
|
self.owner = owner
|
||||||
|
self.name = name
|
||||||
|
self.public_key = public_key
|
||||||
|
self.fingerprint = fingerprint
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
return self.manager.delete_key_pair(self.owner, self.name)
|
||||||
|
|
||||||
|
class UserManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
if hasattr(self.__class__, '_instance'):
|
||||||
|
raise Exception('Attempted to instantiate singleton')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def instance(cls):
|
||||||
|
if not hasattr(cls, '_instance'):
|
||||||
|
inst = UserManager()
|
||||||
|
cls._instance = inst
|
||||||
|
if FLAGS.fake_users:
|
||||||
|
try:
|
||||||
|
inst.create_user('fake', 'fake', 'fake')
|
||||||
|
except: pass
|
||||||
|
try:
|
||||||
|
inst.create_user('user', 'user', 'user')
|
||||||
|
except: pass
|
||||||
|
try:
|
||||||
|
inst.create_user('admin', 'admin', 'admin', True)
|
||||||
|
except: pass
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def authenticate(self, params, signature, verb='GET', server_string='127.0.0.1:8773', path='/'):
|
||||||
|
# TODO: Check for valid timestamp
|
||||||
|
access_key = params['AWSAccessKeyId']
|
||||||
|
user = self.get_user_from_access_key(access_key)
|
||||||
|
if user == None:
|
||||||
|
return None
|
||||||
|
# hmac can't handle unicode, so encode ensures that secret isn't unicode
|
||||||
|
expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
|
||||||
|
_log.debug('user.secret: %s', user.secret)
|
||||||
|
_log.debug('expected_signature: %s', expected_signature)
|
||||||
|
_log.debug('signature: %s', signature)
|
||||||
|
if signature == expected_signature:
|
||||||
|
return user
|
||||||
|
|
||||||
|
def has_role(self, user, role, project=None):
|
||||||
|
# Map role to ldap group
|
||||||
|
group = FLAGS.__getitem__("ldap_%s" % role)
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.is_member_of(user, group)
|
||||||
|
|
||||||
|
def add_role(self, user, role, project=None):
|
||||||
|
# TODO: Project-specific roles
|
||||||
|
group = FLAGS.__getitem__("ldap_%s" % role)
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.add_to_group(user, group)
|
||||||
|
|
||||||
|
def get_user(self, uid):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.find_user(uid)
|
||||||
|
|
||||||
|
def get_user_from_access_key(self, access_key):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.find_user_by_access_key(access_key)
|
||||||
|
|
||||||
|
def get_users(self):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.find_users()
|
||||||
|
|
||||||
|
def create_user(self, uid, access=None, secret=None, admin=False):
|
||||||
|
if access == None: access = str(uuid.uuid4())
|
||||||
|
if secret == None: secret = str(uuid.uuid4())
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
u = conn.create_user(uid, access, secret, admin)
|
||||||
|
return u
|
||||||
|
|
||||||
|
def delete_user(self, uid):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
conn.delete_user(uid)
|
||||||
|
|
||||||
|
def generate_key_pair(self, uid, key_name):
|
||||||
|
# generating key pair is slow so delay generation
|
||||||
|
# until after check
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
if not conn.user_exists(uid):
|
||||||
|
raise UserError("User " + uid + " doesn't exist")
|
||||||
|
if conn.key_pair_exists(uid, key_name):
|
||||||
|
raise InvalidKeyPair("The keypair '" +
|
||||||
|
key_name +
|
||||||
|
"' already exists.",
|
||||||
|
"Duplicate")
|
||||||
|
private_key, public_key, fingerprint = crypto.generate_key_pair()
|
||||||
|
self.create_key_pair(uid, key_name, public_key, fingerprint)
|
||||||
|
return private_key, fingerprint
|
||||||
|
|
||||||
|
def create_key_pair(self, uid, key_name, public_key, fingerprint):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.create_key_pair(uid, key_name, public_key, fingerprint)
|
||||||
|
|
||||||
|
def get_key_pair(self, uid, key_name):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.find_key_pair(uid, key_name)
|
||||||
|
|
||||||
|
def get_key_pairs(self, uid):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
return conn.find_key_pairs(uid)
|
||||||
|
|
||||||
|
def delete_key_pair(self, uid, key_name):
|
||||||
|
with LDAPWrapper() as conn:
|
||||||
|
conn.delete_key_pair(uid, key_name)
|
||||||
|
|
||||||
|
def get_signed_zip(self, uid):
|
||||||
|
user = self.get_user(uid)
|
||||||
|
return user.get_credentials()
|
||||||
|
|
||||||
|
def generate_x509_cert(self, uid):
|
||||||
|
(private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(uid))
|
||||||
|
# TODO - This should be async call back to the cloud controller
|
||||||
|
signed_cert = crypto.sign_csr(csr, uid)
|
||||||
|
return (private_key, signed_cert)
|
||||||
|
|
||||||
|
def sign_cert(self, csr, uid):
|
||||||
|
return crypto.sign_csr(csr, uid)
|
||||||
|
|
||||||
|
def __cert_subject(self, uid):
|
||||||
|
return "/C=US/ST=California/L=The_Mission/O=AnsoLabs/OU=Nova/CN=%s-%s" % (uid, str(datetime.datetime.utcnow().isoformat()))
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPWrapper(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.user = FLAGS.user_dn
|
||||||
|
self.passwd = FLAGS.ldap_password
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.connect()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
#logging.info('type, value, traceback: %s, %s, %s', type, value, traceback)
|
||||||
|
self.conn.unbind_s()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
""" connect to ldap as admin user """
|
||||||
|
if FLAGS.fake_users:
|
||||||
|
self.conn = fakeldap.initialize(FLAGS.ldap_url)
|
||||||
|
else:
|
||||||
|
assert(ldap.__name__ != 'fakeldap')
|
||||||
|
self.conn = ldap.initialize(FLAGS.ldap_url)
|
||||||
|
self.conn.simple_bind_s(self.user, self.passwd)
|
||||||
|
|
||||||
|
def find_object(self, dn, query = None):
|
||||||
|
objects = self.find_objects(dn, query)
|
||||||
|
if len(objects) == 0:
|
||||||
|
return None
|
||||||
|
return objects[0]
|
||||||
|
|
||||||
|
def find_objects(self, dn, query = None):
|
||||||
|
try:
|
||||||
|
res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
# just return the attributes
|
||||||
|
return [x[1] for x in res]
|
||||||
|
|
||||||
|
def find_users(self):
|
||||||
|
attrs = self.find_objects(FLAGS.ldap_subtree, '(objectclass=novaUser)')
|
||||||
|
return [self.__to_user(attr) for attr in attrs]
|
||||||
|
|
||||||
|
def find_key_pairs(self, uid):
|
||||||
|
dn = 'uid=%s,%s' % (uid, FLAGS.ldap_subtree)
|
||||||
|
attrs = self.find_objects(dn, '(objectclass=novaKeyPair)')
|
||||||
|
return [self.__to_key_pair(uid, attr) for attr in attrs]
|
||||||
|
|
||||||
|
def find_user(self, name):
|
||||||
|
dn = 'uid=%s,%s' % (name, FLAGS.ldap_subtree)
|
||||||
|
attr = self.find_object(dn, '(objectclass=novaUser)')
|
||||||
|
return self.__to_user(attr)
|
||||||
|
|
||||||
|
def user_exists(self, name):
|
||||||
|
return self.find_user(name) != None
|
||||||
|
|
||||||
|
def find_key_pair(self, uid, key_name):
|
||||||
|
dn = 'cn=%s,uid=%s,%s' % (key_name,
|
||||||
|
uid,
|
||||||
|
FLAGS.ldap_subtree)
|
||||||
|
attr = self.find_object(dn, '(objectclass=novaKeyPair)')
|
||||||
|
return self.__to_key_pair(uid, attr)
|
||||||
|
|
||||||
|
def delete_key_pairs(self, uid):
|
||||||
|
keys = self.find_key_pairs(uid)
|
||||||
|
if keys != None:
|
||||||
|
for key in keys:
|
||||||
|
self.delete_key_pair(uid, key.name)
|
||||||
|
|
||||||
|
def key_pair_exists(self, uid, key_name):
|
||||||
|
return self.find_key_pair(uid, key_name) != None
|
||||||
|
|
||||||
|
def create_user(self, name, access_key, secret_key, is_admin):
|
||||||
|
if self.user_exists(name):
|
||||||
|
raise UserError("LDAP user " + name + " already exists")
|
||||||
|
attr = [
|
||||||
|
('objectclass', ['person',
|
||||||
|
'organizationalPerson',
|
||||||
|
'inetOrgPerson',
|
||||||
|
'novaUser']),
|
||||||
|
('ou', [FLAGS.user_unit]),
|
||||||
|
('uid', [name]),
|
||||||
|
('sn', [name]),
|
||||||
|
('cn', [name]),
|
||||||
|
('secretKey', [secret_key]),
|
||||||
|
('accessKey', [access_key]),
|
||||||
|
('isAdmin', [str(is_admin).upper()]),
|
||||||
|
]
|
||||||
|
self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree),
|
||||||
|
attr)
|
||||||
|
return self.__to_user(dict(attr))
|
||||||
|
|
||||||
|
def create_project(self, name, project_manager):
|
||||||
|
# PM can be user object or string containing DN
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_member_of(self, name, group):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def add_to_group(self, name, group):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def remove_from_group(self, name, group):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_key_pair(self, uid, key_name, public_key, fingerprint):
|
||||||
|
"""create's a public key in the directory underneath the user"""
|
||||||
|
# TODO(vish): possibly refactor this to store keys in their own ou
|
||||||
|
# and put dn reference in the user object
|
||||||
|
attr = [
|
||||||
|
('objectclass', ['novaKeyPair']),
|
||||||
|
('cn', [key_name]),
|
||||||
|
('sshPublicKey', [public_key]),
|
||||||
|
('keyFingerprint', [fingerprint]),
|
||||||
|
]
|
||||||
|
self.conn.add_s('cn=%s,uid=%s,%s' % (key_name,
|
||||||
|
uid,
|
||||||
|
FLAGS.ldap_subtree),
|
||||||
|
attr)
|
||||||
|
return self.__to_key_pair(uid, dict(attr))
|
||||||
|
|
||||||
|
def find_user_by_access_key(self, access):
|
||||||
|
query = '(' + 'accessKey' + '=' + access + ')'
|
||||||
|
dn = FLAGS.ldap_subtree
|
||||||
|
return self.__to_user(self.find_object(dn, query))
|
||||||
|
|
||||||
|
def delete_key_pair(self, uid, key_name):
|
||||||
|
if not self.key_pair_exists(uid, key_name):
|
||||||
|
raise UserError("Key Pair " +
|
||||||
|
key_name +
|
||||||
|
" doesn't exist for user " +
|
||||||
|
uid)
|
||||||
|
self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid,
|
||||||
|
FLAGS.ldap_subtree))
|
||||||
|
|
||||||
|
def delete_user(self, name):
|
||||||
|
if not self.user_exists(name):
|
||||||
|
raise UserError("User " +
|
||||||
|
name +
|
||||||
|
" doesn't exist")
|
||||||
|
self.delete_key_pairs(name)
|
||||||
|
self.conn.delete_s('uid=%s,%s' % (name,
|
||||||
|
FLAGS.ldap_subtree))
|
||||||
|
|
||||||
|
def __to_user(self, attr):
|
||||||
|
if attr == None:
|
||||||
|
return None
|
||||||
|
return User(
|
||||||
|
id = attr['uid'][0],
|
||||||
|
name = attr['uid'][0],
|
||||||
|
access = attr['accessKey'][0],
|
||||||
|
secret = attr['secretKey'][0],
|
||||||
|
admin = (attr['isAdmin'][0] == 'TRUE')
|
||||||
|
)
|
||||||
|
|
||||||
|
def __to_key_pair(self, owner, attr):
|
||||||
|
if attr == None:
|
||||||
|
return None
|
||||||
|
return KeyPair(
|
||||||
|
owner = owner,
|
||||||
|
name = attr['cn'][0],
|
||||||
|
public_key = attr['sshPublicKey'][0],
|
||||||
|
fingerprint = attr['keyFingerprint'][0],
|
||||||
|
)
|
367
nova/datastore.py
Normal file
367
nova/datastore.py
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Datastore:
|
||||||
|
|
||||||
|
Providers the Keeper class, a simple pseudo-dictionary that
|
||||||
|
persists on disk.
|
||||||
|
|
||||||
|
MAKE Sure that ReDIS is running, and your flags are set properly,
|
||||||
|
before trying to run this.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import redis
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import utils
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_string('datastore_path', utils.abspath('../keeper'),
|
||||||
|
'where keys are stored on disk')
|
||||||
|
flags.DEFINE_string('redis_host', '127.0.0.1',
|
||||||
|
'Host that redis is running on.')
|
||||||
|
flags.DEFINE_integer('redis_port', 6379,
|
||||||
|
'Port that redis is running on.')
|
||||||
|
flags.DEFINE_integer('redis_db', 0, 'Multiple DB keeps tests away')
|
||||||
|
flags.DEFINE_string('keeper_backend', 'redis',
|
||||||
|
'which backend to use for keeper')
|
||||||
|
|
||||||
|
|
||||||
|
class Redis(object):
|
||||||
|
def __init__(self):
|
||||||
|
if hasattr(self.__class__, '_instance'):
|
||||||
|
raise Exception('Attempted to instantiate singleton')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def instance(cls):
|
||||||
|
if not hasattr(cls, '_instance'):
|
||||||
|
inst = redis.Redis(host=FLAGS.redis_host, port=FLAGS.redis_port, db=FLAGS.redis_db)
|
||||||
|
cls._instance = inst
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
|
class RedisModel(object):
|
||||||
|
""" Wrapper around redis-backed properties """
|
||||||
|
object_type = 'generic'
|
||||||
|
def __init__(self, object_id):
|
||||||
|
""" loads an object from the datastore if exists """
|
||||||
|
self.object_id = object_id
|
||||||
|
self.initial_state = {}
|
||||||
|
self.state = Redis.instance().hgetall(self.__redis_key)
|
||||||
|
if self.state:
|
||||||
|
self.initial_state = self.state
|
||||||
|
else:
|
||||||
|
self.set_default_state()
|
||||||
|
|
||||||
|
def set_default_state(self):
|
||||||
|
self.state = {'state' : 'pending'}
|
||||||
|
self.state[self.object_type+"_id"] = self.object_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __redis_key(self):
|
||||||
|
""" Magic string for instance keys """
|
||||||
|
return '%s:%s' % (self.object_type, self.object_id)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s:%s>" % (self.object_type, self.object_id)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.state)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.state.keys()
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
copyDict = {}
|
||||||
|
for item in self.keys():
|
||||||
|
copyDict[item] = self[item]
|
||||||
|
return copyDict
|
||||||
|
|
||||||
|
def get(self, item, default):
|
||||||
|
return self.state.get(item, default)
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return self.state[item]
|
||||||
|
|
||||||
|
def __setitem__(self, item, val):
|
||||||
|
self.state[item] = val
|
||||||
|
return self.state[item]
|
||||||
|
|
||||||
|
def __delitem__(self, item):
|
||||||
|
""" We don't support this """
|
||||||
|
raise Exception("Silly monkey, we NEED all our properties.")
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
""" update the directory with the state from this instance """
|
||||||
|
# TODO(ja): implement hmset in redis-py and use it
|
||||||
|
# instead of multiple calls to hset
|
||||||
|
for key, val in self.state.iteritems():
|
||||||
|
# if (not self.initial_state.has_key(key)
|
||||||
|
# or self.initial_state[key] != val):
|
||||||
|
Redis.instance().hset(self.__redis_key, key, val)
|
||||||
|
if self.initial_state == {}:
|
||||||
|
self.first_save()
|
||||||
|
self.initial_state = self.state
|
||||||
|
return True
|
||||||
|
|
||||||
|
def first_save(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
""" deletes all related records from datastore.
|
||||||
|
does NOT do anything to running state.
|
||||||
|
"""
|
||||||
|
Redis.instance().delete(self.__redis_key)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(key, prefix=None):
|
||||||
|
"""
|
||||||
|
Key has to be a valid filename. Slugify solves that.
|
||||||
|
"""
|
||||||
|
return "%s%s" % (prefix, key)
|
||||||
|
|
||||||
|
|
||||||
|
class SqliteKeeper(object):
|
||||||
|
""" Keeper implementation in SQLite, mostly for in-memory testing """
|
||||||
|
_conn = {} # class variable
|
||||||
|
|
||||||
|
def __init__(self, prefix):
|
||||||
|
self.prefix = prefix
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conn(self):
|
||||||
|
if self.prefix not in self.__class__._conn:
|
||||||
|
logging.debug('no sqlite connection (%s), making new', self.prefix)
|
||||||
|
if FLAGS.datastore_path != ':memory:':
|
||||||
|
try:
|
||||||
|
os.mkdir(FLAGS.datastore_path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
conn = sqlite3.connect(os.path.join(
|
||||||
|
FLAGS.datastore_path, '%s.sqlite' % self.prefix))
|
||||||
|
else:
|
||||||
|
conn = sqlite3.connect(':memory:')
|
||||||
|
|
||||||
|
c = conn.cursor()
|
||||||
|
try:
|
||||||
|
c.execute('''CREATE TABLE data (item text, value text)''')
|
||||||
|
conn.commit()
|
||||||
|
except Exception:
|
||||||
|
logging.exception('create table failed')
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
self.__class__._conn[self.prefix] = conn
|
||||||
|
|
||||||
|
return self.__class__._conn[self.prefix]
|
||||||
|
|
||||||
|
def __delitem__(self, item):
|
||||||
|
#logging.debug('sqlite deleting %s', item)
|
||||||
|
c = self.conn.cursor()
|
||||||
|
try:
|
||||||
|
c.execute('DELETE FROM data WHERE item = ?', (item, ))
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception:
|
||||||
|
logging.exception('delete failed: %s', item)
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
#logging.debug('sqlite getting %s', item)
|
||||||
|
result = None
|
||||||
|
c = self.conn.cursor()
|
||||||
|
try:
|
||||||
|
c.execute('SELECT value FROM data WHERE item = ?', (item, ))
|
||||||
|
row = c.fetchone()
|
||||||
|
if row:
|
||||||
|
result = json.loads(row[0])
|
||||||
|
else:
|
||||||
|
result = None
|
||||||
|
except Exception:
|
||||||
|
logging.exception('select failed: %s', item)
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
#logging.debug('sqlite got %s: %s', item, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __setitem__(self, item, value):
|
||||||
|
serialized_value = json.dumps(value)
|
||||||
|
insert = True
|
||||||
|
if self[item] is not None:
|
||||||
|
insert = False
|
||||||
|
#logging.debug('sqlite insert %s: %s', item, value)
|
||||||
|
c = self.conn.cursor()
|
||||||
|
try:
|
||||||
|
if insert:
|
||||||
|
c.execute('INSERT INTO data VALUES (?, ?)',
|
||||||
|
(item, serialized_value))
|
||||||
|
else:
|
||||||
|
c.execute('UPDATE data SET item=?, value=? WHERE item = ?',
|
||||||
|
(item, serialized_value, item))
|
||||||
|
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception:
|
||||||
|
logging.exception('select failed: %s', item)
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
if self.prefix not in self.__class__._conn:
|
||||||
|
return
|
||||||
|
self.conn.close()
|
||||||
|
if FLAGS.datastore_path != ':memory:':
|
||||||
|
os.unlink(os.path.join(FLAGS.datastore_path, '%s.sqlite' % self.prefix))
|
||||||
|
del self.__class__._conn[self.prefix]
|
||||||
|
|
||||||
|
def clear_all(self):
|
||||||
|
for k, conn in self.__class__._conn.iteritems():
|
||||||
|
conn.close()
|
||||||
|
if FLAGS.datastore_path != ':memory:':
|
||||||
|
os.unlink(os.path.join(FLAGS.datastore_path,
|
||||||
|
'%s.sqlite' % self.prefix))
|
||||||
|
self.__class__._conn = {}
|
||||||
|
|
||||||
|
|
||||||
|
def set_add(self, item, value):
|
||||||
|
group = self[item]
|
||||||
|
if not group:
|
||||||
|
group = []
|
||||||
|
group.append(value)
|
||||||
|
self[item] = group
|
||||||
|
|
||||||
|
def set_is_member(self, item, value):
|
||||||
|
group = self[item]
|
||||||
|
if not group:
|
||||||
|
return False
|
||||||
|
return value in group
|
||||||
|
|
||||||
|
def set_remove(self, item, value):
|
||||||
|
group = self[item]
|
||||||
|
if not group:
|
||||||
|
group = []
|
||||||
|
group.remove(value)
|
||||||
|
self[item] = group
|
||||||
|
|
||||||
|
def set_fetch(self, item):
|
||||||
|
# TODO(termie): I don't really know what set_fetch is supposed to do
|
||||||
|
group = self[item]
|
||||||
|
if not group:
|
||||||
|
group = []
|
||||||
|
return iter(group)
|
||||||
|
|
||||||
|
class JsonKeeper(object):
|
||||||
|
"""
|
||||||
|
Simple dictionary class that persists using
|
||||||
|
JSON in files saved to disk.
|
||||||
|
"""
|
||||||
|
def __init__(self, prefix):
|
||||||
|
self.prefix = prefix
|
||||||
|
|
||||||
|
def __delitem__(self, item):
|
||||||
|
"""
|
||||||
|
Removing a key means deleting a file from disk.
|
||||||
|
"""
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
path = "%s/%s" % (FLAGS.datastore_path, item)
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
"""
|
||||||
|
Fetch file contents and dejsonify them.
|
||||||
|
"""
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
path = "%s/%s" % (FLAGS.datastore_path, item)
|
||||||
|
if os.path.isfile(path):
|
||||||
|
return json.load(open(path, 'r'))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setitem__(self, item, value):
|
||||||
|
"""
|
||||||
|
JSON encode value and save to file.
|
||||||
|
"""
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
path = "%s/%s" % (FLAGS.datastore_path, item)
|
||||||
|
with open(path, "w") as blobfile:
|
||||||
|
blobfile.write(json.dumps(value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class RedisKeeper(object):
|
||||||
|
"""
|
||||||
|
Simple dictionary class that persists using
|
||||||
|
ReDIS.
|
||||||
|
"""
|
||||||
|
def __init__(self, prefix="redis-"):
|
||||||
|
self.prefix = prefix
|
||||||
|
Redis.instance().ping()
|
||||||
|
|
||||||
|
def __setitem__(self, item, value):
|
||||||
|
"""
|
||||||
|
JSON encode value and save to file.
|
||||||
|
"""
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
Redis.instance().set(item, json.dumps(value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
value = Redis.instance().get(item)
|
||||||
|
if value:
|
||||||
|
return json.loads(value)
|
||||||
|
|
||||||
|
def __delitem__(self, item):
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
return Redis.instance().delete(item)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def clear_all(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def set_add(self, item, value):
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
return Redis.instance().sadd(item, json.dumps(value))
|
||||||
|
|
||||||
|
def set_is_member(self, item, value):
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
return Redis.instance().sismember(item, json.dumps(value))
|
||||||
|
|
||||||
|
def set_remove(self, item, value):
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
return Redis.instance().srem(item, json.dumps(value))
|
||||||
|
|
||||||
|
def set_fetch(self, item):
|
||||||
|
item = slugify(item, self.prefix)
|
||||||
|
for obj in Redis.instance().sinter([item]):
|
||||||
|
yield json.loads(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def Keeper(prefix=''):
|
||||||
|
KEEPERS = {'redis': RedisKeeper,
|
||||||
|
'sqlite': SqliteKeeper}
|
||||||
|
return KEEPERS[FLAGS.keeper_backend](prefix)
|
||||||
|
|
28
nova/endpoint/__init__.py
Normal file
28
nova/endpoint/__init__.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
:mod:`nova.endpoint` -- Main NOVA Api endpoints
|
||||||
|
=====================================================
|
||||||
|
|
||||||
|
.. automodule:: nova.endpoint
|
||||||
|
:platform: Unix
|
||||||
|
:synopsis: REST APIs for all nova functions
|
||||||
|
.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
|
||||||
|
.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
|
||||||
|
.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
|
||||||
|
.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
|
||||||
|
.. moduleauthor:: Manish Singh <yosh@gimp.org>
|
||||||
|
.. moduleauthor:: Andy Smith <andy@anarkystic.com>
|
||||||
|
"""
|
131
nova/endpoint/admin.py
Normal file
131
nova/endpoint/admin.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Admin API controller, exposed through http via the api worker.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def user_dict(user, base64_file=None):
|
||||||
|
"""Convert the user object to a result dict"""
|
||||||
|
if user:
|
||||||
|
return {
|
||||||
|
'username': user.id,
|
||||||
|
'accesskey': user.access,
|
||||||
|
'secretkey': user.secret,
|
||||||
|
'file': base64_file,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def node_dict(node):
|
||||||
|
"""Convert a node object to a result dict"""
|
||||||
|
if node:
|
||||||
|
return {
|
||||||
|
'node_id': node.id,
|
||||||
|
'workers': ", ".join(node.workers),
|
||||||
|
'disks': ", ".join(node.disks),
|
||||||
|
'ram': node.memory,
|
||||||
|
'load_average' : node.load_average,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def admin_only(target):
|
||||||
|
"""Decorator for admin-only API calls"""
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
"""Internal wrapper method for admin-only API calls"""
|
||||||
|
context = args[1]
|
||||||
|
if context.user.is_admin():
|
||||||
|
return target(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
class AdminController(object):
|
||||||
|
"""
|
||||||
|
API Controller for users, node status, and worker mgmt.
|
||||||
|
Trivial admin_only wrapper will be replaced with RBAC,
|
||||||
|
allowing project managers to administer project users.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, user_manager, node_manager=None):
|
||||||
|
self.user_manager = user_manager
|
||||||
|
self.node_manager = node_manager
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'AdminController'
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
def describe_user(self, _context, name, **_kwargs):
|
||||||
|
"""Returns user data, including access and secret keys.
|
||||||
|
"""
|
||||||
|
return user_dict(self.user_manager.get_user(name))
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
def describe_users(self, _context, **_kwargs):
|
||||||
|
"""Returns all users - should be changed to deal with a list.
|
||||||
|
"""
|
||||||
|
return {'userSet':
|
||||||
|
[user_dict(u) for u in self.user_manager.get_users()] }
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
def register_user(self, _context, name, **_kwargs):
|
||||||
|
""" Creates a new user, and returns generated credentials.
|
||||||
|
"""
|
||||||
|
self.user_manager.create_user(name)
|
||||||
|
|
||||||
|
return user_dict(self.user_manager.get_user(name))
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
def deregister_user(self, _context, name, **_kwargs):
|
||||||
|
"""Deletes a single user (NOT undoable.)
|
||||||
|
Should throw an exception if the user has instances,
|
||||||
|
volumes, or buckets remaining.
|
||||||
|
"""
|
||||||
|
self.user_manager.delete_user(name)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
def generate_x509_for_user(self, _context, name, **_kwargs):
|
||||||
|
"""Generates and returns an x509 certificate for a single user.
|
||||||
|
Is usually called from a client that will wrap this with
|
||||||
|
access and secret key info, and return a zip file.
|
||||||
|
"""
|
||||||
|
user = self.user_manager.get_user(name)
|
||||||
|
return user_dict(user, base64.b64encode(user.get_credentials()))
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
def describe_nodes(self, _context, **_kwargs):
|
||||||
|
"""Returns status info for all nodes. Includes:
|
||||||
|
* Disk Space
|
||||||
|
* Instance List
|
||||||
|
* RAM used
|
||||||
|
* CPU used
|
||||||
|
* DHCP servers running
|
||||||
|
* Iptables / bridges
|
||||||
|
"""
|
||||||
|
return {'nodeSet':
|
||||||
|
[node_dict(n) for n in self.node_manager.get_nodes()] }
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
def describe_node(self, _context, name, **_kwargs):
|
||||||
|
"""Returns status info for single node.
|
||||||
|
"""
|
||||||
|
return node_dict(self.node_manager.get_node(name))
|
||||||
|
|
337
nova/endpoint/api.py
Executable file
337
nova/endpoint/api.py
Executable file
@ -0,0 +1,337 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tornado REST API Request Handlers for Nova functions
|
||||||
|
Most calls are proxied into the responsible controller.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import multiprocessing
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import urllib
|
||||||
|
# TODO(termie): replace minidom with etree
|
||||||
|
from xml.dom import minidom
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import tornado.web
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from nova import crypto
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import utils
|
||||||
|
from nova.endpoint import cloud
|
||||||
|
from nova.auth import users
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
|
||||||
|
|
||||||
|
|
||||||
|
_log = logging.getLogger("api")
|
||||||
|
_log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
|
||||||
|
|
||||||
|
|
||||||
|
def _camelcase_to_underscore(str):
|
||||||
|
return _c2u.sub(r'_\1', str).lower().strip('_')
|
||||||
|
|
||||||
|
|
||||||
|
def _underscore_to_camelcase(str):
|
||||||
|
return ''.join([x[:1].upper() + x[1:] for x in str.split('_')])
|
||||||
|
|
||||||
|
|
||||||
|
def _underscore_to_xmlcase(str):
|
||||||
|
res = _underscore_to_camelcase(str)
|
||||||
|
return res[:1].lower() + res[1:]
|
||||||
|
|
||||||
|
|
||||||
|
class APIRequestContext(object):
|
||||||
|
def __init__(self, handler, user):
|
||||||
|
self.handler = handler
|
||||||
|
self.user = user
|
||||||
|
self.request_id = ''.join(
|
||||||
|
[random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
|
||||||
|
for x in xrange(20)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class APIRequest(object):
|
||||||
|
def __init__(self, handler, controller, action):
|
||||||
|
self.handler = handler
|
||||||
|
self.controller = controller
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
def send(self, user, **kwargs):
|
||||||
|
context = APIRequestContext(self.handler, user)
|
||||||
|
|
||||||
|
try:
|
||||||
|
method = getattr(self.controller,
|
||||||
|
_camelcase_to_underscore(self.action))
|
||||||
|
except AttributeError:
|
||||||
|
_error = ('Unsupported API request: controller = %s,'
|
||||||
|
'action = %s') % (self.controller, self.action)
|
||||||
|
_log.warning(_error)
|
||||||
|
# TODO: Raise custom exception, trap in apiserver,
|
||||||
|
# and reraise as 400 error.
|
||||||
|
raise Exception(_error)
|
||||||
|
|
||||||
|
args = {}
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
parts = key.split(".")
|
||||||
|
key = _camelcase_to_underscore(parts[0])
|
||||||
|
if len(parts) > 1:
|
||||||
|
d = args.get(key, {})
|
||||||
|
d[parts[1]] = value[0]
|
||||||
|
value = d
|
||||||
|
else:
|
||||||
|
value = value[0]
|
||||||
|
args[key] = value
|
||||||
|
|
||||||
|
for key in args.keys():
|
||||||
|
if isinstance(args[key], dict):
|
||||||
|
if args[key] != {} and args[key].keys()[0].isdigit():
|
||||||
|
s = args[key].items()
|
||||||
|
s.sort()
|
||||||
|
args[key] = [v for k, v in s]
|
||||||
|
|
||||||
|
d = defer.maybeDeferred(method, context, **args)
|
||||||
|
d.addCallback(self._render_response, context.request_id)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def _render_response(self, response_data, request_id):
|
||||||
|
xml = minidom.Document()
|
||||||
|
|
||||||
|
response_el = xml.createElement(self.action + 'Response')
|
||||||
|
response_el.setAttribute('xmlns',
|
||||||
|
'http://ec2.amazonaws.com/doc/2009-11-30/')
|
||||||
|
request_id_el = xml.createElement('requestId')
|
||||||
|
request_id_el.appendChild(xml.createTextNode(request_id))
|
||||||
|
response_el.appendChild(request_id_el)
|
||||||
|
if(response_data == True):
|
||||||
|
self._render_dict(xml, response_el, {'return': 'true'})
|
||||||
|
else:
|
||||||
|
self._render_dict(xml, response_el, response_data)
|
||||||
|
|
||||||
|
xml.appendChild(response_el)
|
||||||
|
|
||||||
|
response = xml.toxml()
|
||||||
|
xml.unlink()
|
||||||
|
_log.debug(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _render_dict(self, xml, el, data):
|
||||||
|
try:
|
||||||
|
for key in data.keys():
|
||||||
|
val = data[key]
|
||||||
|
el.appendChild(self._render_data(xml, key, val))
|
||||||
|
except:
|
||||||
|
_log.debug(data)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _render_data(self, xml, el_name, data):
|
||||||
|
el_name = _underscore_to_xmlcase(el_name)
|
||||||
|
data_el = xml.createElement(el_name)
|
||||||
|
|
||||||
|
if isinstance(data, list):
|
||||||
|
for item in data:
|
||||||
|
data_el.appendChild(self._render_data(xml, 'item', item))
|
||||||
|
elif isinstance(data, dict):
|
||||||
|
self._render_dict(xml, data_el, data)
|
||||||
|
elif hasattr(data, '__dict__'):
|
||||||
|
self._render_dict(xml, data_el, data.__dict__)
|
||||||
|
elif isinstance(data, bool):
|
||||||
|
data_el.appendChild(xml.createTextNode(str(data).lower()))
|
||||||
|
elif data != None:
|
||||||
|
data_el.appendChild(xml.createTextNode(str(data)))
|
||||||
|
|
||||||
|
return data_el
|
||||||
|
|
||||||
|
|
||||||
|
class RootRequestHandler(tornado.web.RequestHandler):
|
||||||
|
def get(self):
|
||||||
|
# available api versions
|
||||||
|
versions = [
|
||||||
|
'1.0',
|
||||||
|
'2007-01-19',
|
||||||
|
'2007-03-01',
|
||||||
|
'2007-08-29',
|
||||||
|
'2007-10-10',
|
||||||
|
'2007-12-15',
|
||||||
|
'2008-02-01',
|
||||||
|
'2008-09-01',
|
||||||
|
'2009-04-04',
|
||||||
|
]
|
||||||
|
for version in versions:
|
||||||
|
self.write('%s\n' % version)
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataRequestHandler(tornado.web.RequestHandler):
|
||||||
|
def print_data(self, data):
|
||||||
|
if isinstance(data, dict):
|
||||||
|
output = ''
|
||||||
|
for key in data:
|
||||||
|
if key == '_name':
|
||||||
|
continue
|
||||||
|
output += key
|
||||||
|
if isinstance(data[key], dict):
|
||||||
|
if '_name' in data[key]:
|
||||||
|
output += '=' + str(data[key]['_name'])
|
||||||
|
else:
|
||||||
|
output += '/'
|
||||||
|
output += '\n'
|
||||||
|
self.write(output[:-1]) # cut off last \n
|
||||||
|
elif isinstance(data, list):
|
||||||
|
self.write('\n'.join(data))
|
||||||
|
else:
|
||||||
|
self.write(str(data))
|
||||||
|
|
||||||
|
def lookup(self, path, data):
|
||||||
|
items = path.split('/')
|
||||||
|
for item in items:
|
||||||
|
if item:
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return data
|
||||||
|
if not item in data:
|
||||||
|
return None
|
||||||
|
data = data[item]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get(self, path):
|
||||||
|
cc = self.application.controllers['Cloud']
|
||||||
|
meta_data = cc.get_metadata(self.request.remote_ip)
|
||||||
|
if meta_data is None:
|
||||||
|
_log.error('Failed to get metadata for ip: %s' %
|
||||||
|
self.request.remote_ip)
|
||||||
|
raise tornado.web.HTTPError(404)
|
||||||
|
data = self.lookup(path, meta_data)
|
||||||
|
if data is None:
|
||||||
|
raise tornado.web.HTTPError(404)
|
||||||
|
self.print_data(data)
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
|
||||||
|
class APIRequestHandler(tornado.web.RequestHandler):
|
||||||
|
def get(self, controller_name):
|
||||||
|
self.execute(controller_name)
|
||||||
|
|
||||||
|
@tornado.web.asynchronous
|
||||||
|
def execute(self, controller_name):
|
||||||
|
# Obtain the appropriate controller for this request.
|
||||||
|
try:
|
||||||
|
controller = self.application.controllers[controller_name]
|
||||||
|
except KeyError:
|
||||||
|
self._error('unhandled', 'no controller named %s' % controller_name)
|
||||||
|
return
|
||||||
|
|
||||||
|
args = self.request.arguments
|
||||||
|
|
||||||
|
# Read request signature.
|
||||||
|
try:
|
||||||
|
signature = args.pop('Signature')[0]
|
||||||
|
except:
|
||||||
|
raise tornado.web.HTTPError(400)
|
||||||
|
|
||||||
|
# Make a copy of args for authentication and signature verification.
|
||||||
|
auth_params = {}
|
||||||
|
for key, value in args.items():
|
||||||
|
auth_params[key] = value[0]
|
||||||
|
|
||||||
|
# Get requested action and remove authentication args for final request.
|
||||||
|
try:
|
||||||
|
action = args.pop('Action')[0]
|
||||||
|
args.pop('AWSAccessKeyId')
|
||||||
|
args.pop('SignatureMethod')
|
||||||
|
args.pop('SignatureVersion')
|
||||||
|
args.pop('Version')
|
||||||
|
args.pop('Timestamp')
|
||||||
|
except:
|
||||||
|
raise tornado.web.HTTPError(400)
|
||||||
|
|
||||||
|
# Authenticate the request.
|
||||||
|
user = self.application.user_manager.authenticate(
|
||||||
|
auth_params,
|
||||||
|
signature,
|
||||||
|
self.request.method,
|
||||||
|
self.request.host,
|
||||||
|
self.request.path
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise tornado.web.HTTPError(403)
|
||||||
|
|
||||||
|
_log.debug('action: %s' % action)
|
||||||
|
|
||||||
|
for key, value in args.items():
|
||||||
|
_log.debug('arg: %s\t\tval: %s' % (key, value))
|
||||||
|
|
||||||
|
request = APIRequest(self, controller, action)
|
||||||
|
d = request.send(user, **args)
|
||||||
|
# d.addCallback(utils.debug)
|
||||||
|
|
||||||
|
# TODO: Wrap response in AWS XML format
|
||||||
|
d.addCallbacks(self._write_callback, self._error_callback)
|
||||||
|
|
||||||
|
def _write_callback(self, data):
|
||||||
|
self.set_header('Content-Type', 'text/xml')
|
||||||
|
self.write(data)
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
def _error_callback(self, failure):
|
||||||
|
try:
|
||||||
|
failure.raiseException()
|
||||||
|
except exception.ApiError as ex:
|
||||||
|
self._error(type(ex).__name__ + "." + ex.code, ex.message)
|
||||||
|
# TODO(vish): do something more useful with unknown exceptions
|
||||||
|
except Exception as ex:
|
||||||
|
self._error(type(ex).__name__, str(ex))
|
||||||
|
raise
|
||||||
|
|
||||||
|
def post(self, controller_name):
|
||||||
|
self.execute(controller_name)
|
||||||
|
|
||||||
|
def _error(self, code, message):
|
||||||
|
self._status_code = 400
|
||||||
|
self.set_header('Content-Type', 'text/xml')
|
||||||
|
self.write('<?xml version="1.0"?>\n')
|
||||||
|
self.write('<Response><Errors><Error><Code>%s</Code>'
|
||||||
|
'<Message>%s</Message></Error></Errors>'
|
||||||
|
'<RequestID>?</RequestID></Response>' % (code, message))
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
|
||||||
|
class APIServerApplication(tornado.web.Application):
|
||||||
|
def __init__(self, user_manager, controllers):
|
||||||
|
tornado.web.Application.__init__(self, [
|
||||||
|
(r'/', RootRequestHandler),
|
||||||
|
(r'/services/([A-Za-z0-9]+)/', APIRequestHandler),
|
||||||
|
(r'/latest/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2009-04-04/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2008-09-01/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2008-02-01/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2007-12-15/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2007-10-10/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2007-08-29/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2007-03-01/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/2007-01-19/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
(r'/1.0/([-A-Za-z0-9/]*)', MetadataRequestHandler),
|
||||||
|
], pool=multiprocessing.Pool(4))
|
||||||
|
self.user_manager = user_manager
|
||||||
|
self.controllers = controllers
|
572
nova/endpoint/cloud.py
Normal file
572
nova/endpoint/cloud.py
Normal file
@ -0,0 +1,572 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cloud Controller: Implementation of EC2 REST API calls, which are
|
||||||
|
dispatched to other nodes via AMQP RPC. State is via distributed
|
||||||
|
datastore.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from nova import datastore
|
||||||
|
from nova import flags
|
||||||
|
from nova import rpc
|
||||||
|
from nova import utils
|
||||||
|
from nova import exception
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.compute import model
|
||||||
|
from nova.compute import network
|
||||||
|
from nova.endpoint import images
|
||||||
|
from nova.volume import storage
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
flags.DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
|
||||||
|
|
||||||
|
def _gen_key(user_id, key_name):
|
||||||
|
""" Tuck this into UserManager """
|
||||||
|
try:
|
||||||
|
manager = users.UserManager.instance()
|
||||||
|
private_key, fingerprint = manager.generate_key_pair(user_id, key_name)
|
||||||
|
except Exception as ex:
|
||||||
|
return {'exception': ex}
|
||||||
|
return {'private_key': private_key, 'fingerprint': fingerprint}
|
||||||
|
|
||||||
|
|
||||||
|
class CloudController(object):
|
||||||
|
""" CloudController provides the critical dispatch between
|
||||||
|
inbound API calls through the endpoint and messages
|
||||||
|
sent to the other nodes.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self._instances = datastore.Keeper(FLAGS.instances_prefix)
|
||||||
|
self.instdir = model.InstanceDirectory()
|
||||||
|
self.network = network.NetworkController()
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def instances(self):
|
||||||
|
""" All instances in the system, as dicts """
|
||||||
|
for instance in self.instdir.all:
|
||||||
|
yield {instance['instance_id']: instance}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volumes(self):
|
||||||
|
""" returns a list of all volumes """
|
||||||
|
for volume_id in datastore.Redis.instance().smembers("volumes"):
|
||||||
|
volume = storage.Volume(volume_id=volume_id)
|
||||||
|
yield volume
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'CloudController'
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
""" Ensure the keychains and folders exist. """
|
||||||
|
# Create keys folder, if it doesn't exist
|
||||||
|
if not os.path.exists(FLAGS.keys_path):
|
||||||
|
os.makedirs(os.path.abspath(FLAGS.keys_path))
|
||||||
|
# Gen root CA, if we don't have one
|
||||||
|
root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file)
|
||||||
|
if not os.path.exists(root_ca_path):
|
||||||
|
start = os.getcwd()
|
||||||
|
os.chdir(FLAGS.ca_path)
|
||||||
|
utils.runthis("Generating root CA: %s", "sh genrootca.sh")
|
||||||
|
os.chdir(start)
|
||||||
|
# TODO: Do this with M2Crypto instead
|
||||||
|
|
||||||
|
def get_instance_by_ip(self, ip):
|
||||||
|
return self.instdir.by_ip(ip)
|
||||||
|
|
||||||
|
def get_metadata(self, ip):
|
||||||
|
i = self.instdir.by_ip(ip)
|
||||||
|
if i is None:
|
||||||
|
return None
|
||||||
|
if i['key_name']:
|
||||||
|
keys = {
|
||||||
|
'0': {
|
||||||
|
'_name': i['key_name'],
|
||||||
|
'openssh-key': i['key_data']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
keys = ''
|
||||||
|
data = {
|
||||||
|
'user-data': base64.b64decode(i['user_data']),
|
||||||
|
'meta-data': {
|
||||||
|
'ami-id': i['image_id'],
|
||||||
|
'ami-launch-index': i['ami_launch_index'],
|
||||||
|
'ami-manifest-path': 'FIXME', # image property
|
||||||
|
'block-device-mapping': { # TODO: replace with real data
|
||||||
|
'ami': 'sda1',
|
||||||
|
'ephemeral0': 'sda2',
|
||||||
|
'root': '/dev/sda1',
|
||||||
|
'swap': 'sda3'
|
||||||
|
},
|
||||||
|
'hostname': i['private_dns_name'], # is this public sometimes?
|
||||||
|
'instance-action': 'none',
|
||||||
|
'instance-id': i['instance_id'],
|
||||||
|
'instance-type': i.get('instance_type', ''),
|
||||||
|
'local-hostname': i['private_dns_name'],
|
||||||
|
'local-ipv4': i['private_dns_name'], # TODO: switch to IP
|
||||||
|
'kernel-id': i.get('kernel_id', ''),
|
||||||
|
'placement': {
|
||||||
|
'availaibility-zone': i.get('availability_zone', 'nova'),
|
||||||
|
},
|
||||||
|
'public-hostname': i.get('dns_name', ''),
|
||||||
|
'public-ipv4': i.get('dns_name', ''), # TODO: switch to IP
|
||||||
|
'public-keys' : keys,
|
||||||
|
'ramdisk-id': i.get('ramdisk_id', ''),
|
||||||
|
'reservation-id': i['reservation_id'],
|
||||||
|
'security-groups': i.get('groups', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if False: # TODO: store ancestor ids
|
||||||
|
data['ancestor-ami-ids'] = []
|
||||||
|
if i.get('product_codes', None):
|
||||||
|
data['product-codes'] = i['product_codes']
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def describe_availability_zones(self, context, **kwargs):
|
||||||
|
return {'availabilityZoneInfo': [{'zoneName': 'nova',
|
||||||
|
'zoneState': 'available'}]}
|
||||||
|
|
||||||
|
def describe_key_pairs(self, context, key_name=None, **kwargs):
|
||||||
|
key_pairs = []
|
||||||
|
key_names = key_name and key_name or []
|
||||||
|
if len(key_names) > 0:
|
||||||
|
for key_name in key_names:
|
||||||
|
key_pair = context.user.get_key_pair(key_name)
|
||||||
|
if key_pair != None:
|
||||||
|
key_pairs.append({
|
||||||
|
'keyName': key_pair.name,
|
||||||
|
'keyFingerprint': key_pair.fingerprint,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
for key_pair in context.user.get_key_pairs():
|
||||||
|
key_pairs.append({
|
||||||
|
'keyName': key_pair.name,
|
||||||
|
'keyFingerprint': key_pair.fingerprint,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { 'keypairsSet': key_pairs }
|
||||||
|
|
||||||
|
def create_key_pair(self, context, key_name, **kwargs):
|
||||||
|
try:
|
||||||
|
d = defer.Deferred()
|
||||||
|
p = context.handler.application.settings.get('pool')
|
||||||
|
def _complete(kwargs):
|
||||||
|
if 'exception' in kwargs:
|
||||||
|
d.errback(kwargs['exception'])
|
||||||
|
return
|
||||||
|
d.callback({'keyName': key_name,
|
||||||
|
'keyFingerprint': kwargs['fingerprint'],
|
||||||
|
'keyMaterial': kwargs['private_key']})
|
||||||
|
p.apply_async(_gen_key, [context.user.id, key_name],
|
||||||
|
callback=_complete)
|
||||||
|
return d
|
||||||
|
|
||||||
|
except users.UserError, e:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def delete_key_pair(self, context, key_name, **kwargs):
|
||||||
|
context.user.delete_key_pair(key_name)
|
||||||
|
# aws returns true even if the key doens't exist
|
||||||
|
return True
|
||||||
|
|
||||||
|
def describe_security_groups(self, context, group_names, **kwargs):
|
||||||
|
groups = { 'securityGroupSet': [] }
|
||||||
|
|
||||||
|
# Stubbed for now to unblock other things.
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def create_security_group(self, context, group_name, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def delete_security_group(self, context, group_name, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_console_output(self, context, instance_id, **kwargs):
|
||||||
|
# instance_id is passed in as a list of instances
|
||||||
|
instance = self.instdir.get(instance_id[0])
|
||||||
|
if instance['state'] == 'pending':
|
||||||
|
raise exception.ApiError('Cannot get output for pending instance')
|
||||||
|
if not context.user.is_authorized(instance.get('owner_id', None)):
|
||||||
|
raise exception.ApiError('Not authorized to view output')
|
||||||
|
return rpc.call('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
|
||||||
|
{"method": "get_console_output",
|
||||||
|
"args" : {"instance_id": instance_id[0]}})
|
||||||
|
|
||||||
|
def _get_user_id(self, context):
|
||||||
|
if context and context.user:
|
||||||
|
return context.user.id
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def describe_volumes(self, context, **kwargs):
|
||||||
|
volumes = []
|
||||||
|
for volume in self.volumes:
|
||||||
|
if context.user.is_authorized(volume.get('user_id', None)):
|
||||||
|
v = self.format_volume(context, volume)
|
||||||
|
volumes.append(v)
|
||||||
|
return defer.succeed({'volumeSet': volumes})
|
||||||
|
|
||||||
|
def format_volume(self, context, volume):
|
||||||
|
v = {}
|
||||||
|
v['volumeId'] = volume['volume_id']
|
||||||
|
v['status'] = volume['status']
|
||||||
|
v['size'] = volume['size']
|
||||||
|
v['availabilityZone'] = volume['availability_zone']
|
||||||
|
v['createTime'] = volume['create_time']
|
||||||
|
if context.user.is_admin():
|
||||||
|
v['status'] = '%s (%s, %s, %s, %s)' % (
|
||||||
|
volume.get('status', None),
|
||||||
|
volume.get('user_id', None),
|
||||||
|
volume.get('node_name', None),
|
||||||
|
volume.get('instance_id', ''),
|
||||||
|
volume.get('mountpoint', ''))
|
||||||
|
return v
|
||||||
|
|
||||||
|
def create_volume(self, context, size, **kwargs):
|
||||||
|
# TODO(vish): refactor this to create the volume object here and tell storage to create it
|
||||||
|
res = rpc.call(FLAGS.storage_topic, {"method": "create_volume",
|
||||||
|
"args" : {"size": size,
|
||||||
|
"user_id": context.user.id}})
|
||||||
|
def _format_result(result):
|
||||||
|
volume = self._get_volume(result['result'])
|
||||||
|
return {'volumeSet': [self.format_volume(context, volume)]}
|
||||||
|
res.addCallback(_format_result)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _get_by_id(self, nodes, id):
|
||||||
|
if nodes == {}:
|
||||||
|
raise exception.NotFound("%s not found" % id)
|
||||||
|
for node_name, node in nodes.iteritems():
|
||||||
|
if node.has_key(id):
|
||||||
|
return node_name, node[id]
|
||||||
|
raise exception.NotFound("%s not found" % id)
|
||||||
|
|
||||||
|
def _get_volume(self, volume_id):
|
||||||
|
for volume in self.volumes:
|
||||||
|
if volume['volume_id'] == volume_id:
|
||||||
|
return volume
|
||||||
|
|
||||||
|
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
|
||||||
|
volume = self._get_volume(volume_id)
|
||||||
|
storage_node = volume['node_name']
|
||||||
|
# TODO: (joshua) Fix volumes to store creator id
|
||||||
|
if not context.user.is_authorized(volume.get('user_id', None)):
|
||||||
|
raise exception.ApiError("%s not authorized for %s" %
|
||||||
|
(context.user.id, volume_id))
|
||||||
|
instance = self.instdir.get(instance_id)
|
||||||
|
compute_node = instance['node_name']
|
||||||
|
if not context.user.is_authorized(instance.get('owner_id', None)):
|
||||||
|
raise exception.ApiError(message="%s not authorized for %s" %
|
||||||
|
(context.user.id, instance_id))
|
||||||
|
aoe_device = volume['aoe_device']
|
||||||
|
# Needs to get right node controller for attaching to
|
||||||
|
# TODO: Maybe have another exchange that goes to everyone?
|
||||||
|
rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
|
||||||
|
{"method": "attach_volume",
|
||||||
|
"args" : {"aoe_device": aoe_device,
|
||||||
|
"instance_id" : instance_id,
|
||||||
|
"mountpoint" : device}})
|
||||||
|
rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
|
||||||
|
{"method": "attach_volume",
|
||||||
|
"args" : {"volume_id": volume_id,
|
||||||
|
"instance_id" : instance_id,
|
||||||
|
"mountpoint" : device}})
|
||||||
|
return defer.succeed(True)
|
||||||
|
|
||||||
|
def detach_volume(self, context, volume_id, **kwargs):
|
||||||
|
# TODO(joshua): Make sure the updated state has been received first
|
||||||
|
volume = self._get_volume(volume_id)
|
||||||
|
storage_node = volume['node_name']
|
||||||
|
if not context.user.is_authorized(volume.get('user_id', None)):
|
||||||
|
raise exception.ApiError("%s not authorized for %s" %
|
||||||
|
(context.user.id, volume_id))
|
||||||
|
if 'instance_id' in volume.keys():
|
||||||
|
instance_id = volume['instance_id']
|
||||||
|
try:
|
||||||
|
instance = self.instdir.get(instance_id)
|
||||||
|
compute_node = instance['node_name']
|
||||||
|
mountpoint = volume['mountpoint']
|
||||||
|
if not context.user.is_authorized(
|
||||||
|
instance.get('owner_id', None)):
|
||||||
|
raise exception.ApiError(
|
||||||
|
"%s not authorized for %s" %
|
||||||
|
(context.user.id, instance_id))
|
||||||
|
rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
|
||||||
|
{"method": "detach_volume",
|
||||||
|
"args" : {"instance_id": instance_id,
|
||||||
|
"mountpoint": mountpoint}})
|
||||||
|
except exception.NotFound:
|
||||||
|
pass
|
||||||
|
rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
|
||||||
|
{"method": "detach_volume",
|
||||||
|
"args" : {"volume_id": volume_id}})
|
||||||
|
return defer.succeed(True)
|
||||||
|
|
||||||
|
def _convert_to_set(self, lst, str):
|
||||||
|
if lst == None or lst == []:
|
||||||
|
return None
|
||||||
|
return [{str: x} for x in lst]
|
||||||
|
|
||||||
|
def describe_instances(self, context, **kwargs):
|
||||||
|
return defer.succeed(self.format_instances(context.user))
|
||||||
|
|
||||||
|
def format_instances(self, user, reservation_id = None):
|
||||||
|
if self.instances == {}:
|
||||||
|
return {'reservationSet': []}
|
||||||
|
reservations = {}
|
||||||
|
for inst in self.instances:
|
||||||
|
instance = inst.values()[0]
|
||||||
|
res_id = instance.get('reservation_id', 'Unknown')
|
||||||
|
if (user.is_authorized(instance.get('owner_id', None))
|
||||||
|
and (reservation_id == None or reservation_id == res_id)):
|
||||||
|
i = {}
|
||||||
|
i['instance_id'] = instance.get('instance_id', None)
|
||||||
|
i['image_id'] = instance.get('image_id', None)
|
||||||
|
i['instance_state'] = {
|
||||||
|
'code': 42,
|
||||||
|
'name': instance.get('state', 'pending')
|
||||||
|
}
|
||||||
|
i['public_dns_name'] = self.network.get_public_ip_for_instance(
|
||||||
|
i['instance_id'])
|
||||||
|
i['private_dns_name'] = instance.get('private_dns_name', None)
|
||||||
|
if not i['public_dns_name']:
|
||||||
|
i['public_dns_name'] = i['private_dns_name']
|
||||||
|
i['dns_name'] = instance.get('dns_name', None)
|
||||||
|
i['key_name'] = instance.get('key_name', None)
|
||||||
|
if user.is_admin():
|
||||||
|
i['key_name'] = '%s (%s, %s)' % (i['key_name'],
|
||||||
|
instance.get('owner_id', None), instance.get('node_name',''))
|
||||||
|
i['product_codes_set'] = self._convert_to_set(
|
||||||
|
instance.get('product_codes', None), 'product_code')
|
||||||
|
i['instance_type'] = instance.get('instance_type', None)
|
||||||
|
i['launch_time'] = instance.get('launch_time', None)
|
||||||
|
i['ami_launch_index'] = instance.get('ami_launch_index',
|
||||||
|
None)
|
||||||
|
if not reservations.has_key(res_id):
|
||||||
|
r = {}
|
||||||
|
r['reservation_id'] = res_id
|
||||||
|
r['owner_id'] = instance.get('owner_id', None)
|
||||||
|
r['group_set'] = self._convert_to_set(
|
||||||
|
instance.get('groups', None), 'group_id')
|
||||||
|
r['instances_set'] = []
|
||||||
|
reservations[res_id] = r
|
||||||
|
reservations[res_id]['instances_set'].append(i)
|
||||||
|
|
||||||
|
instance_response = {'reservationSet' : list(reservations.values()) }
|
||||||
|
return instance_response
|
||||||
|
|
||||||
|
def describe_addresses(self, context, **kwargs):
|
||||||
|
return self.format_addresses(context.user)
|
||||||
|
|
||||||
|
def format_addresses(self, user):
|
||||||
|
addresses = []
|
||||||
|
# TODO(vish): move authorization checking into network.py
|
||||||
|
for address_record in self.network.describe_addresses(
|
||||||
|
type=network.PublicNetwork):
|
||||||
|
#logging.debug(address_record)
|
||||||
|
if user.is_authorized(address_record[u'user_id']):
|
||||||
|
address = {
|
||||||
|
'public_ip': address_record[u'address'],
|
||||||
|
'instance_id' : address_record.get(u'instance_id', 'free')
|
||||||
|
}
|
||||||
|
# FIXME: add another field for user id
|
||||||
|
if user.is_admin():
|
||||||
|
address['instance_id'] = "%s (%s)" % (
|
||||||
|
address['instance_id'],
|
||||||
|
address_record[u'user_id'],
|
||||||
|
)
|
||||||
|
addresses.append(address)
|
||||||
|
# logging.debug(addresses)
|
||||||
|
return {'addressesSet': addresses}
|
||||||
|
|
||||||
|
def allocate_address(self, context, **kwargs):
|
||||||
|
# TODO: Verify user is valid?
|
||||||
|
kwargs['owner_id'] = context.user.id
|
||||||
|
(address,network_name) = self.network.allocate_address(
|
||||||
|
context.user.id, type=network.PublicNetwork)
|
||||||
|
return defer.succeed({'addressSet': [{'publicIp' : address}]})
|
||||||
|
|
||||||
|
def release_address(self, context, **kwargs):
|
||||||
|
self.network.deallocate_address(kwargs.get('public_ip', None))
|
||||||
|
return defer.succeed({'releaseResponse': ["Address released."]})
|
||||||
|
|
||||||
|
def associate_address(self, context, instance_id, **kwargs):
|
||||||
|
instance = self.instdir.get(instance_id)
|
||||||
|
rv = self.network.associate_address(
|
||||||
|
kwargs['public_ip'],
|
||||||
|
instance['private_dns_name'],
|
||||||
|
instance_id)
|
||||||
|
return defer.succeed({'associateResponse': ["Address associated."]})
|
||||||
|
|
||||||
|
def disassociate_address(self, context, **kwargs):
|
||||||
|
rv = self.network.disassociate_address(kwargs['public_ip'])
|
||||||
|
# TODO - Strip the IP from the instance
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def run_instances(self, context, **kwargs):
|
||||||
|
logging.debug("Going to run instances...")
|
||||||
|
reservation_id = utils.generate_uid('r')
|
||||||
|
launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
||||||
|
key_data = None
|
||||||
|
if kwargs.has_key('key_name'):
|
||||||
|
key_pair = context.user.get_key_pair(kwargs['key_name'])
|
||||||
|
if not key_pair:
|
||||||
|
raise exception.ApiError('Key Pair %s not found' %
|
||||||
|
kwargs['key_name'])
|
||||||
|
key_data = key_pair.public_key
|
||||||
|
|
||||||
|
for num in range(int(kwargs['max_count'])):
|
||||||
|
inst = self.instdir.new()
|
||||||
|
# TODO(ja): add ari, aki
|
||||||
|
inst['image_id'] = kwargs['image_id']
|
||||||
|
inst['user_data'] = kwargs.get('user_data', '')
|
||||||
|
inst['instance_type'] = kwargs.get('instance_type', '')
|
||||||
|
inst['reservation_id'] = reservation_id
|
||||||
|
inst['launch_time'] = launch_time
|
||||||
|
inst['key_data'] = key_data or ''
|
||||||
|
inst['key_name'] = kwargs.get('key_name', '')
|
||||||
|
inst['owner_id'] = context.user.id
|
||||||
|
inst['mac_address'] = utils.generate_mac()
|
||||||
|
inst['ami_launch_index'] = num
|
||||||
|
address, _netname = self.network.allocate_address(
|
||||||
|
inst['owner_id'], mac=inst['mac_address'])
|
||||||
|
network = self.network.get_users_network(str(context.user.id))
|
||||||
|
inst['network_str'] = json.dumps(network.to_dict())
|
||||||
|
inst['bridge_name'] = network.bridge_name
|
||||||
|
inst['private_dns_name'] = str(address)
|
||||||
|
# TODO: allocate expresses on the router node
|
||||||
|
inst.save()
|
||||||
|
rpc.cast(FLAGS.compute_topic,
|
||||||
|
{"method": "run_instance",
|
||||||
|
"args": {"instance_id" : inst.instance_id}})
|
||||||
|
logging.debug("Casting to node for %s's instance with IP of %s" %
|
||||||
|
(context.user.name, inst['private_dns_name']))
|
||||||
|
# TODO: Make the NetworkComputeNode figure out the network name from ip.
|
||||||
|
return defer.succeed(self.format_instances(
|
||||||
|
context.user, reservation_id))
|
||||||
|
|
||||||
|
def terminate_instances(self, context, instance_id, **kwargs):
|
||||||
|
logging.debug("Going to start terminating instances")
|
||||||
|
# TODO: return error if not authorized
|
||||||
|
for i in instance_id:
|
||||||
|
logging.debug("Going to try and terminate %s" % i)
|
||||||
|
instance = self.instdir.get(i)
|
||||||
|
#if instance['state'] == 'pending':
|
||||||
|
# raise exception.ApiError('Cannot terminate pending instance')
|
||||||
|
if context.user.is_authorized(instance.get('owner_id', None)):
|
||||||
|
try:
|
||||||
|
self.network.disassociate_address(
|
||||||
|
instance.get('public_dns_name', 'bork'))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if instance.get('private_dns_name', None):
|
||||||
|
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
|
||||||
|
try:
|
||||||
|
self.network.deallocate_address(instance.get('private_dns_name', None))
|
||||||
|
except Exception, _err:
|
||||||
|
pass
|
||||||
|
if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
|
||||||
|
rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
|
||||||
|
{"method": "terminate_instance",
|
||||||
|
"args" : {"instance_id": i}})
|
||||||
|
else:
|
||||||
|
instance.destroy()
|
||||||
|
return defer.succeed(True)
|
||||||
|
|
||||||
|
def reboot_instances(self, context, instance_id, **kwargs):
|
||||||
|
# TODO: return error if not authorized
|
||||||
|
for i in instance_id:
|
||||||
|
instance = self.instdir.get(i)
|
||||||
|
if instance['state'] == 'pending':
|
||||||
|
raise exception.ApiError('Cannot reboot pending instance')
|
||||||
|
if context.user.is_authorized(instance.get('owner_id', None)):
|
||||||
|
rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
|
||||||
|
{"method": "reboot_instance",
|
||||||
|
"args" : {"instance_id": i}})
|
||||||
|
return defer.succeed(True)
|
||||||
|
|
||||||
|
def delete_volume(self, context, volume_id, **kwargs):
|
||||||
|
# TODO: return error if not authorized
|
||||||
|
volume = self._get_volume(volume_id)
|
||||||
|
storage_node = volume['node_name']
|
||||||
|
if context.user.is_authorized(volume.get('user_id', None)):
|
||||||
|
rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
|
||||||
|
{"method": "delete_volume",
|
||||||
|
"args" : {"volume_id": volume_id}})
|
||||||
|
return defer.succeed(True)
|
||||||
|
|
||||||
|
def describe_images(self, context, image_id=None, **kwargs):
|
||||||
|
imageSet = images.list(context.user)
|
||||||
|
if not image_id is None:
|
||||||
|
imageSet = [i for i in imageSet if i['imageId'] in image_id]
|
||||||
|
|
||||||
|
return defer.succeed({'imagesSet': imageSet})
|
||||||
|
|
||||||
|
def deregister_image(self, context, image_id, **kwargs):
|
||||||
|
images.deregister(context.user, image_id)
|
||||||
|
|
||||||
|
return defer.succeed({'imageId': image_id})
|
||||||
|
|
||||||
|
def register_image(self, context, image_location=None, **kwargs):
|
||||||
|
if image_location is None and kwargs.has_key('name'):
|
||||||
|
image_location = kwargs['name']
|
||||||
|
|
||||||
|
image_id = images.register(context.user, image_location)
|
||||||
|
logging.debug("Registered %s as %s" % (image_location, image_id))
|
||||||
|
|
||||||
|
return defer.succeed({'imageId': image_id})
|
||||||
|
|
||||||
|
def modify_image_attribute(self, context, image_id,
|
||||||
|
attribute, operation_type, **kwargs):
|
||||||
|
if attribute != 'launchPermission':
|
||||||
|
raise exception.ApiError('only launchPermission is supported')
|
||||||
|
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
|
||||||
|
raise exception.ApiError('only group "all" is supported')
|
||||||
|
if not operation_type in ['add', 'delete']:
|
||||||
|
raise exception.ApiError('operation_type must be add or delete')
|
||||||
|
result = images.modify(context.user, image_id, operation_type)
|
||||||
|
return defer.succeed(result)
|
||||||
|
|
||||||
|
def update_state(self, topic, value):
|
||||||
|
""" accepts status reports from the queue and consolidates them """
|
||||||
|
# TODO(jmc): if an instance has disappeared from
|
||||||
|
# the node, call instance_death
|
||||||
|
if topic == "instances":
|
||||||
|
return defer.succeed(True)
|
||||||
|
aggregate_state = getattr(self, topic)
|
||||||
|
node_name = value.keys()[0]
|
||||||
|
items = value[node_name]
|
||||||
|
|
||||||
|
logging.debug("Updating %s state for %s" % (topic, node_name))
|
||||||
|
|
||||||
|
for item_id in items.keys():
|
||||||
|
if (aggregate_state.has_key('pending') and
|
||||||
|
aggregate_state['pending'].has_key(item_id)):
|
||||||
|
del aggregate_state['pending'][item_id]
|
||||||
|
aggregate_state[node_name] = items
|
||||||
|
|
||||||
|
return defer.succeed(True)
|
92
nova/endpoint/images.py
Normal file
92
nova/endpoint/images.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Proxy AMI-related calls from the cloud controller, to the running
|
||||||
|
objectstore daemon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import boto
|
||||||
|
import boto.s3
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import utils
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
def modify(user, image_id, operation):
|
||||||
|
conn(user).make_request(
|
||||||
|
method='POST',
|
||||||
|
bucket='_images',
|
||||||
|
query_args=qs({'image_id': image_id, 'operation': operation}))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def register(user, image_location):
|
||||||
|
""" rpc call to register a new image based from a manifest """
|
||||||
|
|
||||||
|
image_id = utils.generate_uid('ami')
|
||||||
|
conn(user).make_request(
|
||||||
|
method='PUT',
|
||||||
|
bucket='_images',
|
||||||
|
query_args=qs({'image_location': image_location,
|
||||||
|
'image_id': image_id}))
|
||||||
|
|
||||||
|
return image_id
|
||||||
|
|
||||||
|
|
||||||
|
def list(user, filter_list=[]):
|
||||||
|
""" return a list of all images that a user can see
|
||||||
|
|
||||||
|
optionally filtered by a list of image_id """
|
||||||
|
|
||||||
|
# FIXME: send along the list of only_images to check for
|
||||||
|
response = conn(user).make_request(
|
||||||
|
method='GET',
|
||||||
|
bucket='_images')
|
||||||
|
|
||||||
|
return json.loads(response.read())
|
||||||
|
|
||||||
|
|
||||||
|
def deregister(user, image_id):
|
||||||
|
""" unregister an image """
|
||||||
|
conn(user).make_request(
|
||||||
|
method='DELETE',
|
||||||
|
bucket='_images',
|
||||||
|
query_args=qs({'image_id': image_id}))
|
||||||
|
|
||||||
|
|
||||||
|
def conn(user):
|
||||||
|
return boto.s3.connection.S3Connection (
|
||||||
|
aws_access_key_id=user.access,
|
||||||
|
aws_secret_access_key=user.secret,
|
||||||
|
is_secure=False,
|
||||||
|
calling_format=boto.s3.connection.OrdinaryCallingFormat(),
|
||||||
|
port=FLAGS.s3_port,
|
||||||
|
host=FLAGS.s3_host)
|
||||||
|
|
||||||
|
|
||||||
|
def qs(params):
|
||||||
|
pairs = []
|
||||||
|
for key in params.keys():
|
||||||
|
pairs.append(key + '=' + urllib.quote(params[key]))
|
||||||
|
return '&'.join(pairs)
|
131
nova/fakerabbit.py
Normal file
131
nova/fakerabbit.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
""" Based a bit on the carrot.backeds.queue backend... but a lot better """
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import Queue as queue
|
||||||
|
|
||||||
|
from carrot.backends import base
|
||||||
|
|
||||||
|
|
||||||
|
class Message(base.BaseMessage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Exchange(object):
|
||||||
|
def __init__(self, name, exchange_type):
|
||||||
|
self.name = name
|
||||||
|
self.exchange_type = exchange_type
|
||||||
|
self._queue = queue.Queue()
|
||||||
|
self._routes = {}
|
||||||
|
|
||||||
|
def publish(self, message, routing_key=None):
|
||||||
|
logging.debug('(%s) publish (key: %s) %s',
|
||||||
|
self.name, routing_key, message)
|
||||||
|
if routing_key in self._routes:
|
||||||
|
for f in self._routes[routing_key]:
|
||||||
|
logging.debug('Publishing to route %s', f)
|
||||||
|
f(message, routing_key=routing_key)
|
||||||
|
|
||||||
|
def bind(self, callback, routing_key):
|
||||||
|
self._routes.setdefault(routing_key, [])
|
||||||
|
self._routes[routing_key].append(callback)
|
||||||
|
|
||||||
|
|
||||||
|
class Queue(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self._queue = queue.Queue()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Queue: %s>' % self.name
|
||||||
|
|
||||||
|
def push(self, message, routing_key=None):
|
||||||
|
self._queue.put(message)
|
||||||
|
|
||||||
|
def size(self):
|
||||||
|
return self._queue.qsize()
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
return self._queue.get()
|
||||||
|
|
||||||
|
|
||||||
|
class Backend(object):
|
||||||
|
""" Singleton backend for testing """
|
||||||
|
class __impl(base.BaseBackend):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
#super(__impl, self).__init__(*args, **kwargs)
|
||||||
|
self._exchanges = {}
|
||||||
|
self._queues = {}
|
||||||
|
|
||||||
|
def _reset_all(self):
|
||||||
|
self._exchanges = {}
|
||||||
|
self._queues = {}
|
||||||
|
|
||||||
|
def queue_declare(self, queue, **kwargs):
|
||||||
|
if queue not in self._queues:
|
||||||
|
logging.debug('Declaring queue %s', queue)
|
||||||
|
self._queues[queue] = Queue(queue)
|
||||||
|
|
||||||
|
def exchange_declare(self, exchange, type, *args, **kwargs):
|
||||||
|
if exchange not in self._exchanges:
|
||||||
|
logging.debug('Declaring exchange %s', exchange)
|
||||||
|
self._exchanges[exchange] = Exchange(exchange, type)
|
||||||
|
|
||||||
|
def queue_bind(self, queue, exchange, routing_key, **kwargs):
|
||||||
|
logging.debug('Binding %s to %s with key %s',
|
||||||
|
queue, exchange, routing_key)
|
||||||
|
self._exchanges[exchange].bind(self._queues[queue].push,
|
||||||
|
routing_key)
|
||||||
|
|
||||||
|
def get(self, queue, no_ack=False):
|
||||||
|
if not self._queues[queue].size():
|
||||||
|
return None
|
||||||
|
(message_data, content_type, content_encoding) = \
|
||||||
|
self._queues[queue].pop()
|
||||||
|
message = Message(backend=self, body=message_data,
|
||||||
|
content_type=content_type,
|
||||||
|
content_encoding=content_encoding)
|
||||||
|
logging.debug('Getting from %s: %s', queue, message)
|
||||||
|
return message
|
||||||
|
|
||||||
|
def prepare_message(self, message_data, delivery_mode,
|
||||||
|
content_type, content_encoding, **kwargs):
|
||||||
|
"""Prepare message for sending."""
|
||||||
|
return (message_data, content_type, content_encoding)
|
||||||
|
|
||||||
|
def publish(self, message, exchange, routing_key, **kwargs):
|
||||||
|
if exchange in self._exchanges:
|
||||||
|
self._exchanges[exchange].publish(
|
||||||
|
message, routing_key=routing_key)
|
||||||
|
|
||||||
|
|
||||||
|
__instance = None
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if Backend.__instance is None:
|
||||||
|
Backend.__instance = Backend.__impl(*args, **kwargs)
|
||||||
|
self.__dict__['_Backend__instance'] = Backend.__instance
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.__instance, attr)
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value):
|
||||||
|
return setattr(self.__instance, attr, value)
|
||||||
|
|
||||||
|
|
||||||
|
def reset_all():
|
||||||
|
Backend()._reset_all()
|
109
nova/fakevirt.py
Normal file
109
nova/fakevirt.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
A fake (in-memory) hypervisor+api. Allows nova testing w/o KVM and libvirt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import StringIO
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
|
||||||
|
class FakeVirtConnection(object):
|
||||||
|
# FIXME: networkCreateXML, listNetworks don't do anything since
|
||||||
|
# they aren't exercised in tests yet
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.next_index = 0
|
||||||
|
self.instances = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def instance(cls):
|
||||||
|
if not hasattr(cls, '_instance'):
|
||||||
|
cls._instance = cls()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def lookupByID(self, i):
|
||||||
|
return self.instances[str(i)]
|
||||||
|
|
||||||
|
def listDomainsID(self):
|
||||||
|
return self.instances.keys()
|
||||||
|
|
||||||
|
def listNetworks(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def lookupByName(self, instance_id):
|
||||||
|
for x in self.instances.values():
|
||||||
|
if x.name() == instance_id:
|
||||||
|
return x
|
||||||
|
raise Exception('no instance found for instance_id: %s' % instance_id)
|
||||||
|
|
||||||
|
def networkCreateXML(self, xml):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def createXML(self, xml, flags):
|
||||||
|
# parse the xml :(
|
||||||
|
xml_stringio = StringIO.StringIO(xml)
|
||||||
|
|
||||||
|
my_xml = ElementTree.parse(xml_stringio)
|
||||||
|
name = my_xml.find('name').text
|
||||||
|
|
||||||
|
fake_instance = FakeVirtInstance(conn=self,
|
||||||
|
index=str(self.next_index),
|
||||||
|
name=name,
|
||||||
|
xml=my_xml)
|
||||||
|
self.instances[str(self.next_index)] = fake_instance
|
||||||
|
self.next_index += 1
|
||||||
|
|
||||||
|
def _removeInstance(self, i):
|
||||||
|
self.instances.pop(str(i))
|
||||||
|
|
||||||
|
|
||||||
|
class FakeVirtInstance(object):
|
||||||
|
NOSTATE = 0x00
|
||||||
|
RUNNING = 0x01
|
||||||
|
BLOCKED = 0x02
|
||||||
|
PAUSED = 0x03
|
||||||
|
SHUTDOWN = 0x04
|
||||||
|
SHUTOFF = 0x05
|
||||||
|
CRASHED = 0x06
|
||||||
|
|
||||||
|
def __init__(self, conn, index, name, xml):
|
||||||
|
self._conn = conn
|
||||||
|
self._destroyed = False
|
||||||
|
self._name = name
|
||||||
|
self._index = index
|
||||||
|
self._state = self.RUNNING
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
if self._state == self.SHUTOFF:
|
||||||
|
raise Exception('instance already destroyed: %s' % self.name())
|
||||||
|
self._state = self.SHUTDOWN
|
||||||
|
self._conn._removeInstance(self._index)
|
||||||
|
|
||||||
|
def info(self):
|
||||||
|
return [self._state, 0, 2, 0, 0]
|
||||||
|
|
||||||
|
def XMLDesc(self, flags):
|
||||||
|
return open('fakevirtinstance.xml', 'r').read()
|
||||||
|
|
||||||
|
def blockStats(self, disk):
|
||||||
|
return [0L, 0L, 0L, 0L, null]
|
||||||
|
|
||||||
|
def interfaceStats(self, iface):
|
||||||
|
return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
|
78
nova/flags.py
Normal file
78
nova/flags.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Package-level global flags are defined here, the rest are defined
|
||||||
|
where they're used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from gflags import *
|
||||||
|
|
||||||
|
# This keeps pylint from barfing on the imports
|
||||||
|
FLAGS = FLAGS
|
||||||
|
DEFINE_string = DEFINE_string
|
||||||
|
DEFINE_integer = DEFINE_integer
|
||||||
|
DEFINE_bool = DEFINE_bool
|
||||||
|
|
||||||
|
# __GLOBAL FLAGS ONLY__
|
||||||
|
# Define any app-specific flags in their own files, docs at:
|
||||||
|
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
|
||||||
|
|
||||||
|
DEFINE_integer('s3_port', 3333, 's3 port')
|
||||||
|
DEFINE_integer('s3_internal_port', 3334, 's3 port')
|
||||||
|
DEFINE_string('s3_host', '127.0.0.1', 's3 host')
|
||||||
|
#DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
|
||||||
|
DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
|
||||||
|
DEFINE_string('storage_topic', 'storage', 'the topic storage nodes listen on')
|
||||||
|
DEFINE_bool('fake_libvirt', False,
|
||||||
|
'whether to use a fake libvirt or not')
|
||||||
|
DEFINE_bool('verbose', False, 'show debug output')
|
||||||
|
DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit')
|
||||||
|
DEFINE_bool('fake_network', False, 'should we use fake network devices and addresses')
|
||||||
|
DEFINE_bool('fake_users', False, 'use fake users')
|
||||||
|
DEFINE_string('rabbit_host', 'localhost', 'rabbit host')
|
||||||
|
DEFINE_integer('rabbit_port', 5672, 'rabbit port')
|
||||||
|
DEFINE_string('rabbit_userid', 'guest', 'rabbit userid')
|
||||||
|
DEFINE_string('rabbit_password', 'guest', 'rabbit password')
|
||||||
|
DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
|
||||||
|
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
|
||||||
|
DEFINE_string('ec2_url',
|
||||||
|
'http://127.0.0.1:8773/services/Cloud',
|
||||||
|
'Url to ec2 api server')
|
||||||
|
|
||||||
|
DEFINE_string('default_image',
|
||||||
|
'ami-11111',
|
||||||
|
'default image to use, testing only')
|
||||||
|
DEFINE_string('default_kernel',
|
||||||
|
'aki-11111',
|
||||||
|
'default kernel to use, testing only')
|
||||||
|
DEFINE_string('default_ramdisk',
|
||||||
|
'ari-11111',
|
||||||
|
'default ramdisk to use, testing only')
|
||||||
|
DEFINE_string('default_instance_type',
|
||||||
|
'm1.small',
|
||||||
|
'default instance type to use, testing only')
|
||||||
|
|
||||||
|
# UNUSED
|
||||||
|
DEFINE_string('node_availability_zone',
|
||||||
|
'nova',
|
||||||
|
'availability zone of this node')
|
||||||
|
DEFINE_string('node_name',
|
||||||
|
socket.gethostname(),
|
||||||
|
'name of this node')
|
||||||
|
|
131
nova/process.py
Normal file
131
nova/process.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Process pool, still buggy right now.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from twisted.internet import defer
|
||||||
|
from twisted.internet import reactor
|
||||||
|
from twisted.internet import protocol
|
||||||
|
from twisted.internet import threads
|
||||||
|
|
||||||
|
# NOTE(termie): this is copied from twisted.internet.utils but since
|
||||||
|
# they don't export it I've copied.
|
||||||
|
class _BackRelay(protocol.ProcessProtocol):
|
||||||
|
"""
|
||||||
|
Trivial protocol for communicating with a process and turning its output
|
||||||
|
into the result of a L{Deferred}.
|
||||||
|
|
||||||
|
@ivar deferred: A L{Deferred} which will be called back with all of stdout
|
||||||
|
and, if C{errortoo} is true, all of stderr as well (mixed together in
|
||||||
|
one string). If C{errortoo} is false and any bytes are received over
|
||||||
|
stderr, this will fire with an L{_UnexpectedErrorOutput} instance and
|
||||||
|
the attribute will be set to C{None}.
|
||||||
|
|
||||||
|
@ivar onProcessEnded: If C{errortoo} is false and bytes are received over
|
||||||
|
stderr, this attribute will refer to a L{Deferred} which will be called
|
||||||
|
back when the process ends. This C{Deferred} is also associated with
|
||||||
|
the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in
|
||||||
|
this case so that users can determine when the process has actually
|
||||||
|
ended, in addition to knowing when bytes have been received via stderr.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, deferred, errortoo=0):
|
||||||
|
self.deferred = deferred
|
||||||
|
self.s = StringIO.StringIO()
|
||||||
|
if errortoo:
|
||||||
|
self.errReceived = self.errReceivedIsGood
|
||||||
|
else:
|
||||||
|
self.errReceived = self.errReceivedIsBad
|
||||||
|
|
||||||
|
def errReceivedIsBad(self, text):
|
||||||
|
if self.deferred is not None:
|
||||||
|
self.onProcessEnded = defer.Deferred()
|
||||||
|
err = _UnexpectedErrorOutput(text, self.onProcessEnded)
|
||||||
|
self.deferred.errback(failure.Failure(err))
|
||||||
|
self.deferred = None
|
||||||
|
self.transport.loseConnection()
|
||||||
|
|
||||||
|
def errReceivedIsGood(self, text):
|
||||||
|
self.s.write(text)
|
||||||
|
|
||||||
|
def outReceived(self, text):
|
||||||
|
self.s.write(text)
|
||||||
|
|
||||||
|
def processEnded(self, reason):
|
||||||
|
if self.deferred is not None:
|
||||||
|
self.deferred.callback(self.s.getvalue())
|
||||||
|
elif self.onProcessEnded is not None:
|
||||||
|
self.onProcessEnded.errback(reason)
|
||||||
|
|
||||||
|
|
||||||
|
class BackRelayWithInput(_BackRelay):
|
||||||
|
def __init__(self, deferred, errortoo=0, input=None):
|
||||||
|
super(BackRelayWithInput, self).__init__(deferred, errortoo)
|
||||||
|
self.input = input
|
||||||
|
|
||||||
|
def connectionMade(self):
|
||||||
|
if self.input:
|
||||||
|
self.transport.write(self.input)
|
||||||
|
self.transport.closeStdin()
|
||||||
|
|
||||||
|
|
||||||
|
def getProcessOutput(executable, args=None, env=None, path=None, reactor=None,
|
||||||
|
errortoo=0, input=None):
|
||||||
|
if reactor is None:
|
||||||
|
from twisted.internet import reactor
|
||||||
|
args = args and args or ()
|
||||||
|
env = env and env and {}
|
||||||
|
d = defer.Deferred()
|
||||||
|
p = BackRelayWithInput(d, errortoo=errortoo, input=input)
|
||||||
|
reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
class Pool(object):
|
||||||
|
""" A simple process pool implementation around mutliprocessing.
|
||||||
|
|
||||||
|
Allows up to `size` processes at a time and queues the rest.
|
||||||
|
|
||||||
|
Using workarounds for multiprocessing behavior described in:
|
||||||
|
http://pypi.python.org/pypi/twisted.internet.processes/1.0b1
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, size=None):
|
||||||
|
self._size = size
|
||||||
|
self._pool = multiprocessing.Pool(size)
|
||||||
|
self._registerShutdown()
|
||||||
|
|
||||||
|
def _registerShutdown(self):
|
||||||
|
reactor.addSystemEventTrigger(
|
||||||
|
'during', 'shutdown', self.shutdown, reactor)
|
||||||
|
|
||||||
|
def shutdown(self, reactor=None):
|
||||||
|
if not self._pool:
|
||||||
|
return
|
||||||
|
self._pool.close()
|
||||||
|
# wait for workers to finish
|
||||||
|
self._pool.terminate()
|
||||||
|
self._pool = None
|
||||||
|
|
||||||
|
def apply(self, f, *args, **kw):
|
||||||
|
""" Add a task to the pool and return a deferred. """
|
||||||
|
result = self._pool.apply_async(f, args, kw)
|
||||||
|
return threads.deferToThread(result.get)
|
222
nova/rpc.py
Normal file
222
nova/rpc.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
AMQP-based RPC. Queues have consumers and publishers.
|
||||||
|
No fan-out support yet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import anyjson
|
||||||
|
from carrot import connection
|
||||||
|
from carrot import messaging
|
||||||
|
from twisted.internet import defer
|
||||||
|
from twisted.internet import reactor
|
||||||
|
from twisted.internet import task
|
||||||
|
|
||||||
|
from nova import fakerabbit
|
||||||
|
from nova import flags
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
_log = logging.getLogger('amqplib')
|
||||||
|
_log.setLevel(logging.WARN)
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(connection.BrokerConnection):
|
||||||
|
@classmethod
|
||||||
|
def instance(cls):
|
||||||
|
if not hasattr(cls, '_instance'):
|
||||||
|
params = dict(hostname=FLAGS.rabbit_host,
|
||||||
|
port=FLAGS.rabbit_port,
|
||||||
|
userid=FLAGS.rabbit_userid,
|
||||||
|
password=FLAGS.rabbit_password,
|
||||||
|
virtual_host=FLAGS.rabbit_virtual_host)
|
||||||
|
|
||||||
|
if FLAGS.fake_rabbit:
|
||||||
|
params['backend_cls'] = fakerabbit.Backend
|
||||||
|
|
||||||
|
cls._instance = cls(**params)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
|
class Consumer(messaging.Consumer):
|
||||||
|
# TODO(termie): it would be nice to give these some way of automatically
|
||||||
|
# cleaning up after themselves
|
||||||
|
def attach_to_tornado(self, io_inst=None):
|
||||||
|
from tornado import ioloop
|
||||||
|
if io_inst is None:
|
||||||
|
io_inst = ioloop.IOLoop.instance()
|
||||||
|
|
||||||
|
injected = ioloop.PeriodicCallback(
|
||||||
|
lambda: self.fetch(enable_callbacks=True), 1, io_loop=io_inst)
|
||||||
|
injected.start()
|
||||||
|
return injected
|
||||||
|
|
||||||
|
attachToTornado = attach_to_tornado
|
||||||
|
|
||||||
|
def attach_to_twisted(self):
|
||||||
|
loop = task.LoopingCall(self.fetch, enable_callbacks=True)
|
||||||
|
loop.start(interval=0.001)
|
||||||
|
|
||||||
|
class Publisher(messaging.Publisher):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TopicConsumer(Consumer):
|
||||||
|
exchange_type = "topic"
|
||||||
|
def __init__(self, connection=None, topic="broadcast"):
|
||||||
|
self.queue = topic
|
||||||
|
self.routing_key = topic
|
||||||
|
self.exchange = FLAGS.control_exchange
|
||||||
|
super(TopicConsumer, self).__init__(connection=connection)
|
||||||
|
|
||||||
|
|
||||||
|
class AdapterConsumer(TopicConsumer):
|
||||||
|
def __init__(self, connection=None, topic="broadcast", proxy=None):
|
||||||
|
_log.debug('Initing the Adapter Consumer for %s' % (topic))
|
||||||
|
self.proxy = proxy
|
||||||
|
super(AdapterConsumer, self).__init__(connection=connection, topic=topic)
|
||||||
|
|
||||||
|
def receive(self, message_data, message):
|
||||||
|
_log.debug('received %s' % (message_data))
|
||||||
|
msg_id = message_data.pop('_msg_id', None)
|
||||||
|
|
||||||
|
method = message_data.get('method')
|
||||||
|
args = message_data.get('args', {})
|
||||||
|
if not method:
|
||||||
|
return
|
||||||
|
|
||||||
|
node_func = getattr(self.proxy, str(method))
|
||||||
|
node_args = dict((str(k), v) for k, v in args.iteritems())
|
||||||
|
d = defer.maybeDeferred(node_func, **node_args)
|
||||||
|
if msg_id:
|
||||||
|
d.addCallback(lambda rval: msg_reply(msg_id, rval))
|
||||||
|
d.addErrback(lambda e: msg_reply(msg_id, str(e)))
|
||||||
|
message.ack()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class TopicPublisher(Publisher):
|
||||||
|
exchange_type = "topic"
|
||||||
|
def __init__(self, connection=None, topic="broadcast"):
|
||||||
|
self.routing_key = topic
|
||||||
|
self.exchange = FLAGS.control_exchange
|
||||||
|
super(TopicPublisher, self).__init__(connection=connection)
|
||||||
|
|
||||||
|
|
||||||
|
class DirectConsumer(Consumer):
|
||||||
|
exchange_type = "direct"
|
||||||
|
def __init__(self, connection=None, msg_id=None):
|
||||||
|
self.queue = msg_id
|
||||||
|
self.routing_key = msg_id
|
||||||
|
self.exchange = msg_id
|
||||||
|
self.auto_delete = True
|
||||||
|
super(DirectConsumer, self).__init__(connection=connection)
|
||||||
|
|
||||||
|
|
||||||
|
class DirectPublisher(Publisher):
|
||||||
|
exchange_type = "direct"
|
||||||
|
def __init__(self, connection=None, msg_id=None):
|
||||||
|
self.routing_key = msg_id
|
||||||
|
self.exchange = msg_id
|
||||||
|
self.auto_delete = True
|
||||||
|
super(DirectPublisher, self).__init__(connection=connection)
|
||||||
|
|
||||||
|
|
||||||
|
def msg_reply(msg_id, reply):
|
||||||
|
conn = Connection.instance()
|
||||||
|
publisher = DirectPublisher(connection=conn, msg_id=msg_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
publisher.send({'result': reply})
|
||||||
|
except TypeError:
|
||||||
|
publisher.send(
|
||||||
|
{'result': dict((k, repr(v))
|
||||||
|
for k, v in reply.__dict__.iteritems())
|
||||||
|
})
|
||||||
|
publisher.close()
|
||||||
|
|
||||||
|
|
||||||
|
def call(topic, msg):
|
||||||
|
_log.debug("Making asynchronous call...")
|
||||||
|
msg_id = uuid.uuid4().hex
|
||||||
|
msg.update({'_msg_id': msg_id})
|
||||||
|
_log.debug("MSG_ID is %s" % (msg_id))
|
||||||
|
|
||||||
|
conn = Connection.instance()
|
||||||
|
d = defer.Deferred()
|
||||||
|
consumer = DirectConsumer(connection=conn, msg_id=msg_id)
|
||||||
|
consumer.register_callback(lambda data, message: d.callback(data))
|
||||||
|
injected = consumer.attach_to_tornado()
|
||||||
|
|
||||||
|
# clean up after the injected listened and return x
|
||||||
|
d.addCallback(lambda x: injected.stop() and x or x)
|
||||||
|
|
||||||
|
publisher = TopicPublisher(connection=conn, topic=topic)
|
||||||
|
publisher.send(msg)
|
||||||
|
publisher.close()
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def cast(topic, msg):
|
||||||
|
_log.debug("Making asynchronous cast...")
|
||||||
|
conn = Connection.instance()
|
||||||
|
publisher = TopicPublisher(connection=conn, topic=topic)
|
||||||
|
publisher.send(msg)
|
||||||
|
publisher.close()
|
||||||
|
|
||||||
|
|
||||||
|
def generic_response(message_data, message):
|
||||||
|
_log.debug('response %s', message_data)
|
||||||
|
message.ack()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def send_message(topic, message, wait=True):
|
||||||
|
msg_id = uuid.uuid4().hex
|
||||||
|
message.update({'_msg_id': msg_id})
|
||||||
|
_log.debug('topic is %s', topic)
|
||||||
|
_log.debug('message %s', message)
|
||||||
|
|
||||||
|
if wait:
|
||||||
|
consumer = messaging.Consumer(connection=rpc.Connection.instance(),
|
||||||
|
queue=msg_id,
|
||||||
|
exchange=msg_id,
|
||||||
|
auto_delete=True,
|
||||||
|
exchange_type="direct",
|
||||||
|
routing_key=msg_id)
|
||||||
|
consumer.register_callback(generic_response)
|
||||||
|
|
||||||
|
publisher = messaging.Publisher(connection=rpc.Connection.instance(),
|
||||||
|
exchange="nova",
|
||||||
|
exchange_type="topic",
|
||||||
|
routing_key=topic)
|
||||||
|
publisher.send(message)
|
||||||
|
publisher.close()
|
||||||
|
|
||||||
|
if wait:
|
||||||
|
consumer.wait()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Replace with a docstring test
|
||||||
|
if __name__ == "__main__":
|
||||||
|
send_message(sys.argv[1], anyjson.deserialize(sys.argv[2]))
|
139
nova/server.py
Normal file
139
nova/server.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base functionality for nova daemons - gradually being replaced with twistd.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import daemon
|
||||||
|
from daemon import pidlockfile
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_bool('daemonize', False, 'daemonize this process')
|
||||||
|
# NOTE(termie): right now I am defaulting to using syslog when we daemonize
|
||||||
|
# it may be better to do something else -shrug-
|
||||||
|
# NOTE(Devin): I think we should let each process have its own log file
|
||||||
|
# and put it in /var/logs/nova/(appname).log
|
||||||
|
# This makes debugging much easier and cuts down on sys log
|
||||||
|
# clutter.
|
||||||
|
flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing')
|
||||||
|
flags.DEFINE_string('logfile', None, 'log file to output to')
|
||||||
|
flags.DEFINE_string('pidfile', None, 'pid file to output to')
|
||||||
|
flags.DEFINE_string('working_directory', './', 'working directory...')
|
||||||
|
|
||||||
|
|
||||||
|
def stop(pidfile):
|
||||||
|
"""
|
||||||
|
Stop the daemon
|
||||||
|
"""
|
||||||
|
# Get the pid from the pidfile
|
||||||
|
try:
|
||||||
|
pf = file(pidfile,'r')
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
pf.close()
|
||||||
|
except IOError:
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
if not pid:
|
||||||
|
message = "pidfile %s does not exist. Daemon not running?\n"
|
||||||
|
sys.stderr.write(message % pidfile)
|
||||||
|
return # not an error in a restart
|
||||||
|
|
||||||
|
# Try killing the daemon process
|
||||||
|
try:
|
||||||
|
while 1:
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
time.sleep(0.1)
|
||||||
|
except OSError, err:
|
||||||
|
err = str(err)
|
||||||
|
if err.find("No such process") > 0:
|
||||||
|
if os.path.exists(pidfile):
|
||||||
|
os.remove(pidfile)
|
||||||
|
else:
|
||||||
|
print str(err)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def serve(name, main):
|
||||||
|
argv = FLAGS(sys.argv)
|
||||||
|
|
||||||
|
if not FLAGS.pidfile:
|
||||||
|
FLAGS.pidfile = '%s.pid' % name
|
||||||
|
|
||||||
|
logging.debug("Full set of FLAGS: \n\n\n" )
|
||||||
|
for flag in FLAGS:
|
||||||
|
logging.debug("%s : %s" % (flag, FLAGS.get(flag, None) ))
|
||||||
|
|
||||||
|
action = 'start'
|
||||||
|
if len(argv) > 1:
|
||||||
|
action = argv.pop()
|
||||||
|
|
||||||
|
if action == 'stop':
|
||||||
|
stop(FLAGS.pidfile)
|
||||||
|
sys.exit()
|
||||||
|
elif action == 'restart':
|
||||||
|
stop(FLAGS.pidfile)
|
||||||
|
elif action == 'start':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print 'usage: %s [options] [start|stop|restart]' % argv[0]
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logging.getLogger('amqplib').setLevel(logging.WARN)
|
||||||
|
if FLAGS.daemonize:
|
||||||
|
logger = logging.getLogger()
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
name + '(%(name)s): %(levelname)s %(message)s')
|
||||||
|
if FLAGS.use_syslog and not FLAGS.logfile:
|
||||||
|
syslog = logging.handlers.SysLogHandler(address='/dev/log')
|
||||||
|
syslog.setFormatter(formatter)
|
||||||
|
logger.addHandler(syslog)
|
||||||
|
else:
|
||||||
|
if not FLAGS.logfile:
|
||||||
|
FLAGS.logfile = '%s.log' % name
|
||||||
|
logfile = logging.handlers.FileHandler(FLAGS.logfile)
|
||||||
|
logfile.setFormatter(formatter)
|
||||||
|
logger.addHandler(logfile)
|
||||||
|
stdin, stdout, stderr = None, None, None
|
||||||
|
else:
|
||||||
|
stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
|
||||||
|
|
||||||
|
if FLAGS.verbose:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
logging.getLogger().setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
with daemon.DaemonContext(
|
||||||
|
detach_process=FLAGS.daemonize,
|
||||||
|
working_directory=FLAGS.working_directory,
|
||||||
|
pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile,
|
||||||
|
acquire_timeout=1,
|
||||||
|
threaded=False),
|
||||||
|
stdin=stdin,
|
||||||
|
stdout=stdout,
|
||||||
|
stderr=stderr
|
||||||
|
):
|
||||||
|
main(argv)
|
60
nova/tests/access_unittest.py
Normal file
60
nova/tests/access_unittest.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import test
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.endpoint import cloud
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
class AccessTestCase(test.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
FLAGS.fake_libvirt = True
|
||||||
|
FLAGS.fake_storage = True
|
||||||
|
self.users = users.UserManager.instance()
|
||||||
|
super(AccessTestCase, self).setUp()
|
||||||
|
# Make a test project
|
||||||
|
# Make a test user
|
||||||
|
self.users.create_user('test1', 'access', 'secret')
|
||||||
|
|
||||||
|
# Make the test user a member of the project
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# Delete the test user
|
||||||
|
# Delete the test project
|
||||||
|
self.users.delete_user('test1')
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_001_basic_user_access(self):
|
||||||
|
user = self.users.get_user('test1')
|
||||||
|
# instance-foo, should be using object and not owner_id
|
||||||
|
instance_id = "i-12345678"
|
||||||
|
self.assertTrue(user.is_authorized(instance_id, action="describe_instances"))
|
||||||
|
|
||||||
|
def test_002_sysadmin_access(self):
|
||||||
|
user = self.users.get_user('test1')
|
||||||
|
bucket = "foo/bar/image"
|
||||||
|
self.assertFalse(user.is_authorized(bucket, action="register"))
|
||||||
|
self.users.add_role(user, "sysadmin")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# TODO: Implement use_fake as an option
|
||||||
|
unittest.main()
|
50
nova/tests/api_integration.py
Normal file
50
nova/tests/api_integration.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 unittest
|
||||||
|
|
||||||
|
import boto
|
||||||
|
from boto.ec2.regioninfo import RegionInfo
|
||||||
|
|
||||||
|
ACCESS_KEY = 'fake'
|
||||||
|
SECRET_KEY = 'fake'
|
||||||
|
CLC_IP = '127.0.0.1'
|
||||||
|
CLC_PORT = 8773
|
||||||
|
REGION = 'test'
|
||||||
|
|
||||||
|
def get_connection():
|
||||||
|
return boto.connect_ec2 (
|
||||||
|
aws_access_key_id=ACCESS_KEY,
|
||||||
|
aws_secret_access_key=SECRET_KEY,
|
||||||
|
is_secure=False,
|
||||||
|
region=RegionInfo(None, REGION, CLC_IP),
|
||||||
|
port=CLC_PORT,
|
||||||
|
path='/services/Cloud',
|
||||||
|
debug=99
|
||||||
|
)
|
||||||
|
|
||||||
|
class APIIntegrationTests(unittest.TestCase):
|
||||||
|
def test_001_get_all_images(self):
|
||||||
|
conn = get_connection()
|
||||||
|
res = conn.get_all_images()
|
||||||
|
print res
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
#print conn.get_all_key_pairs()
|
||||||
|
#print conn.create_key_pair
|
||||||
|
#print conn.create_security_group('name', 'description')
|
||||||
|
|
189
nova/tests/api_unittest.py
Normal file
189
nova/tests/api_unittest.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 httplib
|
||||||
|
import random
|
||||||
|
import StringIO
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import boto
|
||||||
|
from boto.ec2 import regioninfo
|
||||||
|
from tornado import httpserver
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import test
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.endpoint import api
|
||||||
|
from nova.endpoint import cloud
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(termie): These are a bunch of helper methods and classes to short
|
||||||
|
# circuit boto calls and feed them into our tornado handlers,
|
||||||
|
# it's pretty damn circuitous so apologies if you have to fix
|
||||||
|
# a bug in it
|
||||||
|
def boto_to_tornado(method, path, headers, data, host, connection=None):
|
||||||
|
""" translate boto requests into tornado requests
|
||||||
|
|
||||||
|
connection should be a FakeTornadoHttpConnection instance
|
||||||
|
"""
|
||||||
|
headers = httpserver.HTTPHeaders()
|
||||||
|
for k, v in headers.iteritems():
|
||||||
|
headers[k] = v
|
||||||
|
|
||||||
|
req = httpserver.HTTPRequest(method=method,
|
||||||
|
uri=path,
|
||||||
|
headers=headers,
|
||||||
|
body=data,
|
||||||
|
host=host,
|
||||||
|
remote_ip='127.0.0.1',
|
||||||
|
connection=connection)
|
||||||
|
return req
|
||||||
|
|
||||||
|
|
||||||
|
def raw_to_httpresponse(s):
|
||||||
|
""" translate a raw tornado http response into an httplib.HTTPResponse """
|
||||||
|
sock = FakeHttplibSocket(s)
|
||||||
|
resp = httplib.HTTPResponse(sock)
|
||||||
|
resp.begin()
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
class FakeHttplibSocket(object):
|
||||||
|
""" a fake socket implementation for httplib.HTTPResponse, trivial """
|
||||||
|
def __init__(self, s):
|
||||||
|
self.fp = StringIO.StringIO(s)
|
||||||
|
|
||||||
|
def makefile(self, mode, other):
|
||||||
|
return self.fp
|
||||||
|
|
||||||
|
|
||||||
|
class FakeTornadoStream(object):
|
||||||
|
""" a fake stream to satisfy tornado's assumptions, trivial """
|
||||||
|
def set_close_callback(self, f):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FakeTornadoConnection(object):
|
||||||
|
""" a fake connection object for tornado to pass to its handlers
|
||||||
|
|
||||||
|
web requests are expected to write to this as they get data and call
|
||||||
|
finish when they are done with the request, we buffer the writes and
|
||||||
|
kick off a callback when it is done so that we can feed the result back
|
||||||
|
into boto.
|
||||||
|
"""
|
||||||
|
def __init__(self, d):
|
||||||
|
self.d = d
|
||||||
|
self._buffer = StringIO.StringIO()
|
||||||
|
|
||||||
|
def write(self, chunk):
|
||||||
|
self._buffer.write(chunk)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
s = self._buffer.getvalue()
|
||||||
|
self.d.callback(s)
|
||||||
|
|
||||||
|
xheaders = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stream(self):
|
||||||
|
return FakeTornadoStream()
|
||||||
|
|
||||||
|
|
||||||
|
class FakeHttplibConnection(object):
|
||||||
|
""" a fake httplib.HTTPConnection for boto to use
|
||||||
|
|
||||||
|
requests made via this connection actually get translated and routed into
|
||||||
|
our tornado app, we then wait for the response and turn it back into
|
||||||
|
the httplib.HTTPResponse that boto expects.
|
||||||
|
"""
|
||||||
|
def __init__(self, app, host, is_secure=False):
|
||||||
|
self.app = app
|
||||||
|
self.host = host
|
||||||
|
self.deferred = defer.Deferred()
|
||||||
|
|
||||||
|
def request(self, method, path, data, headers):
|
||||||
|
req = boto_to_tornado
|
||||||
|
conn = FakeTornadoConnection(self.deferred)
|
||||||
|
request = boto_to_tornado(connection=conn,
|
||||||
|
method=method,
|
||||||
|
path=path,
|
||||||
|
headers=headers,
|
||||||
|
data=data,
|
||||||
|
host=self.host)
|
||||||
|
handler = self.app(request)
|
||||||
|
self.deferred.addCallback(raw_to_httpresponse)
|
||||||
|
|
||||||
|
def getresponse(self):
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _waiter():
|
||||||
|
result = yield self.deferred
|
||||||
|
defer.returnValue(result)
|
||||||
|
d = _waiter()
|
||||||
|
# NOTE(termie): defer.returnValue above should ensure that
|
||||||
|
# this deferred has already been called by the time
|
||||||
|
# we get here, we are going to cheat and return
|
||||||
|
# the result of the callback
|
||||||
|
return d.result
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ApiEc2TestCase(test.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(ApiEc2TestCase, self).setUp()
|
||||||
|
|
||||||
|
self.users = users.UserManager.instance()
|
||||||
|
self.cloud = cloud.CloudController()
|
||||||
|
|
||||||
|
self.host = '127.0.0.1'
|
||||||
|
|
||||||
|
self.app = api.APIServerApplication(self.users, {'Cloud': self.cloud})
|
||||||
|
self.ec2 = boto.connect_ec2(
|
||||||
|
aws_access_key_id='fake',
|
||||||
|
aws_secret_access_key='fake',
|
||||||
|
is_secure=False,
|
||||||
|
region=regioninfo.RegionInfo(None, 'test', self.host),
|
||||||
|
port=FLAGS.cc_port,
|
||||||
|
path='/services/Cloud')
|
||||||
|
|
||||||
|
self.mox.StubOutWithMock(self.ec2, 'new_http_connection')
|
||||||
|
|
||||||
|
def expect_http(self, host=None, is_secure=False):
|
||||||
|
http = FakeHttplibConnection(
|
||||||
|
self.app, '%s:%d' % (self.host, FLAGS.cc_port), False)
|
||||||
|
self.ec2.new_http_connection(host, is_secure).AndReturn(http)
|
||||||
|
return http
|
||||||
|
|
||||||
|
def test_describe_instances(self):
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
self.assertEqual(self.ec2.get_all_instances(), [])
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_all_key_pairs(self):
|
||||||
|
self.expect_http()
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
keyname = "".join(random.choice("sdiuisudfsdcnpaqwertasd") for x in range(random.randint(4, 8)))
|
||||||
|
self.users.generate_key_pair('fake', keyname)
|
||||||
|
|
||||||
|
rv = self.ec2.get_all_key_pairs()
|
||||||
|
self.assertTrue(filter(lambda k: k.name == keyname, rv))
|
||||||
|
|
161
nova/tests/cloud_unittest.py
Normal file
161
nova/tests/cloud_unittest.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 StringIO
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import mox
|
||||||
|
from tornado import ioloop
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import rpc
|
||||||
|
from nova import test
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.compute import node
|
||||||
|
from nova.endpoint import api
|
||||||
|
from nova.endpoint import cloud
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class CloudTestCase(test.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(CloudTestCase, self).setUp()
|
||||||
|
self.flags(fake_libvirt=True,
|
||||||
|
fake_storage=True,
|
||||||
|
fake_users=True,
|
||||||
|
redis_db=8)
|
||||||
|
|
||||||
|
self.conn = rpc.Connection.instance()
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# set up our cloud
|
||||||
|
self.cloud = cloud.CloudController()
|
||||||
|
self.cloud_consumer = rpc.AdapterConsumer(connection=self.conn,
|
||||||
|
topic=FLAGS.cloud_topic,
|
||||||
|
proxy=self.cloud)
|
||||||
|
self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop))
|
||||||
|
|
||||||
|
# set up a node
|
||||||
|
self.node = node.Node()
|
||||||
|
self.node_consumer = rpc.AdapterConsumer(connection=self.conn,
|
||||||
|
topic=FLAGS.compute_topic,
|
||||||
|
proxy=self.node)
|
||||||
|
self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
|
||||||
|
|
||||||
|
user_mocker = mox.Mox()
|
||||||
|
self.admin = user_mocker.CreateMock(users.User)
|
||||||
|
self.admin.is_authorized(mox.IgnoreArg()).AndReturn(True)
|
||||||
|
self.context = api.APIRequestContext(handler=None,user=self.admin)
|
||||||
|
|
||||||
|
def test_console_output(self):
|
||||||
|
if FLAGS.fake_libvirt:
|
||||||
|
logging.debug("Can't test instances without a real virtual env.")
|
||||||
|
return
|
||||||
|
instance_id = 'foo'
|
||||||
|
inst = yield self.node.run_instance(instance_id)
|
||||||
|
output = yield self.cloud.get_console_output(self.context, [instance_id])
|
||||||
|
logging.debug(output)
|
||||||
|
self.assert_(output)
|
||||||
|
rv = yield self.node.terminate_instance(instance_id)
|
||||||
|
|
||||||
|
def test_run_instances(self):
|
||||||
|
if FLAGS.fake_libvirt:
|
||||||
|
logging.debug("Can't test instances without a real virtual env.")
|
||||||
|
return
|
||||||
|
image_id = FLAGS.default_image
|
||||||
|
instance_type = FLAGS.default_instance_type
|
||||||
|
max_count = 1
|
||||||
|
kwargs = {'image_id': image_id,
|
||||||
|
'instance_type': instance_type,
|
||||||
|
'max_count': max_count}
|
||||||
|
rv = yield self.cloud.run_instances(self.context, **kwargs)
|
||||||
|
# TODO: check for proper response
|
||||||
|
instance = rv['reservationSet'][0][rv['reservationSet'][0].keys()[0]][0]
|
||||||
|
logging.debug("Need to watch instance %s until it's running..." % instance['instance_id'])
|
||||||
|
while True:
|
||||||
|
rv = yield defer.succeed(time.sleep(1))
|
||||||
|
info = self.cloud._get_instance(instance['instance_id'])
|
||||||
|
logging.debug(info['state'])
|
||||||
|
if info['state'] == node.Instance.RUNNING:
|
||||||
|
break
|
||||||
|
self.assert_(rv)
|
||||||
|
|
||||||
|
if not FLAGS.fake_libvirt:
|
||||||
|
time.sleep(45) # Should use boto for polling here
|
||||||
|
for reservations in rv['reservationSet']:
|
||||||
|
# for res_id in reservations.keys():
|
||||||
|
# logging.debug(reservations[res_id])
|
||||||
|
# for instance in reservations[res_id]:
|
||||||
|
for instance in reservations[reservations.keys()[0]]:
|
||||||
|
logging.debug("Terminating instance %s" % instance['instance_id'])
|
||||||
|
rv = yield self.node.terminate_instance(instance['instance_id'])
|
||||||
|
|
||||||
|
def test_instance_update_state(self):
|
||||||
|
def instance(num):
|
||||||
|
return {
|
||||||
|
'reservation_id': 'r-1',
|
||||||
|
'instance_id': 'i-%s' % num,
|
||||||
|
'image_id': 'ami-%s' % num,
|
||||||
|
'private_dns_name': '10.0.0.%s' % num,
|
||||||
|
'dns_name': '10.0.0%s' % num,
|
||||||
|
'ami_launch_index': str(num),
|
||||||
|
'instance_type': 'fake',
|
||||||
|
'availability_zone': 'fake',
|
||||||
|
'key_name': None,
|
||||||
|
'kernel_id': 'fake',
|
||||||
|
'ramdisk_id': 'fake',
|
||||||
|
'groups': ['default'],
|
||||||
|
'product_codes': None,
|
||||||
|
'state': 0x01,
|
||||||
|
'user_data': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = self.cloud.format_instances(self.admin)
|
||||||
|
print rv
|
||||||
|
self.assert_(len(rv['reservationSet']) == 0)
|
||||||
|
|
||||||
|
# simulate launch of 5 instances
|
||||||
|
# self.cloud.instances['pending'] = {}
|
||||||
|
#for i in xrange(5):
|
||||||
|
# inst = instance(i)
|
||||||
|
# self.cloud.instances['pending'][inst['instance_id']] = inst
|
||||||
|
|
||||||
|
#rv = self.cloud.format_instances(self.admin)
|
||||||
|
#self.assert_(len(rv['reservationSet']) == 1)
|
||||||
|
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
|
||||||
|
|
||||||
|
# report 4 nodes each having 1 of the instances
|
||||||
|
#for i in xrange(4):
|
||||||
|
# self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}})
|
||||||
|
|
||||||
|
# one instance should be pending still
|
||||||
|
#self.assert_(len(self.cloud.instances['pending'].keys()) == 1)
|
||||||
|
|
||||||
|
# check that the reservations collapse
|
||||||
|
#rv = self.cloud.format_instances(self.admin)
|
||||||
|
#self.assert_(len(rv['reservationSet']) == 1)
|
||||||
|
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
|
||||||
|
|
||||||
|
# check that we can get metadata for each instance
|
||||||
|
#for i in xrange(4):
|
||||||
|
# data = self.cloud.get_metadata(instance(i)['private_dns_name'])
|
||||||
|
# self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i)
|
60
nova/tests/datastore_unittest.py
Normal file
60
nova/tests/datastore_unittest.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
from nova import test
|
||||||
|
from nova import datastore
|
||||||
|
import random
|
||||||
|
|
||||||
|
class KeeperTestCase(test.BaseTestCase):
|
||||||
|
"""
|
||||||
|
Basic persistence tests for Keeper datastore.
|
||||||
|
Generalize, then use these to support
|
||||||
|
migration to redis / cassandra / multiple stores.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Create a new keeper instance for test keys.
|
||||||
|
"""
|
||||||
|
super(KeeperTestCase, self).__init__(*args, **kwargs)
|
||||||
|
self.keeper = datastore.Keeper('test-')
|
||||||
|
|
||||||
|
def tear_down(self):
|
||||||
|
"""
|
||||||
|
Scrub out test keeper data.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_store_strings(self):
|
||||||
|
"""
|
||||||
|
Confirm that simple strings go in and come out safely.
|
||||||
|
Should also test unicode strings.
|
||||||
|
"""
|
||||||
|
randomstring = ''.join(
|
||||||
|
[random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
|
||||||
|
for _x in xrange(20)]
|
||||||
|
)
|
||||||
|
self.keeper['test_string'] = randomstring
|
||||||
|
self.assertEqual(randomstring, self.keeper['test_string'])
|
||||||
|
|
||||||
|
def test_store_dicts(self):
|
||||||
|
"""
|
||||||
|
Arbitrary dictionaries should be storable.
|
||||||
|
"""
|
||||||
|
test_dict = {'key_one': 'value_one'}
|
||||||
|
self.keeper['test_dict'] = test_dict
|
||||||
|
self.assertEqual(test_dict['key_one'],
|
||||||
|
self.keeper['test_dict']['key_one'])
|
||||||
|
|
||||||
|
def test_sets(self):
|
||||||
|
"""
|
||||||
|
A keeper dict should be self-serializing.
|
||||||
|
"""
|
||||||
|
self.keeper.set_add('test_set', 'foo')
|
||||||
|
test_dict = {'arbitrary': 'dict of stuff'}
|
||||||
|
self.keeper.set_add('test_set', test_dict)
|
||||||
|
self.assertTrue(self.keeper.set_is_member('test_set', 'foo'))
|
||||||
|
self.assertFalse(self.keeper.set_is_member('test_set', 'bar'))
|
||||||
|
self.keeper.set_remove('test_set', 'foo')
|
||||||
|
self.assertFalse(self.keeper.set_is_member('test_set', 'foo'))
|
||||||
|
rv = self.keeper.set_fetch('test_set')
|
||||||
|
self.assertEqual(test_dict, rv.next())
|
||||||
|
self.keeper.set_remove('test_set', test_dict)
|
||||||
|
|
26
nova/tests/fake_flags.py
Normal file
26
nova/tests/fake_flags.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 nova import flags
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
FLAGS.fake_libvirt = True
|
||||||
|
FLAGS.fake_storage = True
|
||||||
|
FLAGS.fake_rabbit = True
|
||||||
|
FLAGS.fake_network = True
|
||||||
|
FLAGS.fake_users = True
|
||||||
|
FLAGS.keeper_backend = 'sqlite'
|
||||||
|
FLAGS.datastore_path = ':memory:'
|
||||||
|
FLAGS.verbose = True
|
74
nova/tests/future_unittest.py
Normal file
74
nova/tests/future_unittest.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 StringIO
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import mox
|
||||||
|
from tornado import ioloop
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from nova import cloud
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import node
|
||||||
|
from nova import rpc
|
||||||
|
from nova import test
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class AdminTestCase(test.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(AdminTestCase, self).setUp()
|
||||||
|
self.flags(fake_libvirt=True,
|
||||||
|
fake_rabbit=True)
|
||||||
|
|
||||||
|
self.conn = rpc.Connection.instance()
|
||||||
|
|
||||||
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# set up our cloud
|
||||||
|
self.cloud = cloud.CloudController()
|
||||||
|
self.cloud_consumer = rpc.AdapterConsumer(connection=self.conn,
|
||||||
|
topic=FLAGS.cloud_topic,
|
||||||
|
proxy=self.cloud)
|
||||||
|
self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop))
|
||||||
|
|
||||||
|
# set up a node
|
||||||
|
self.node = node.Node()
|
||||||
|
self.node_consumer = rpc.AdapterConsumer(connection=self.conn,
|
||||||
|
topic=FLAGS.compute_topic,
|
||||||
|
proxy=self.node)
|
||||||
|
self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
|
||||||
|
|
||||||
|
def test_flush_terminated(self):
|
||||||
|
# Launch an instance
|
||||||
|
|
||||||
|
# Wait until it's running
|
||||||
|
|
||||||
|
# Terminate it
|
||||||
|
|
||||||
|
# Wait until it's terminated
|
||||||
|
|
||||||
|
# Flush terminated nodes
|
||||||
|
|
||||||
|
# ASSERT that it's gone
|
||||||
|
pass
|
57
nova/tests/keeper_unittest.py
Normal file
57
nova/tests/keeper_unittest.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
import random
|
||||||
|
|
||||||
|
from nova import datastore
|
||||||
|
from nova import test
|
||||||
|
|
||||||
|
class KeeperTestCase(test.TrialTestCase):
|
||||||
|
"""
|
||||||
|
Basic persistence tests for Keeper datastore.
|
||||||
|
Generalize, then use these to support
|
||||||
|
migration to redis / cassandra / multiple stores.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(KeeperTestCase, self).setUp()
|
||||||
|
self.keeper = datastore.Keeper('test')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(KeeperTestCase, self).tearDown()
|
||||||
|
self.keeper.clear()
|
||||||
|
|
||||||
|
def test_store_strings(self):
|
||||||
|
"""
|
||||||
|
Confirm that simple strings go in and come out safely.
|
||||||
|
Should also test unicode strings.
|
||||||
|
"""
|
||||||
|
randomstring = ''.join(
|
||||||
|
[random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
|
||||||
|
for _x in xrange(20)]
|
||||||
|
)
|
||||||
|
self.keeper['test_string'] = randomstring
|
||||||
|
self.assertEqual(randomstring, self.keeper['test_string'])
|
||||||
|
|
||||||
|
def test_store_dicts(self):
|
||||||
|
"""
|
||||||
|
Arbitrary dictionaries should be storable.
|
||||||
|
"""
|
||||||
|
test_dict = {'key_one': 'value_one'}
|
||||||
|
self.keeper['test_dict'] = test_dict
|
||||||
|
self.assertEqual(test_dict['key_one'],
|
||||||
|
self.keeper['test_dict']['key_one'])
|
||||||
|
|
||||||
|
def test_sets(self):
|
||||||
|
"""
|
||||||
|
A keeper dict should be self-serializing.
|
||||||
|
"""
|
||||||
|
self.keeper.set_add('test_set', 'foo')
|
||||||
|
test_dict = {'arbitrary': 'dict of stuff'}
|
||||||
|
self.keeper.set_add('test_set', test_dict)
|
||||||
|
self.assertTrue(self.keeper.set_is_member('test_set', 'foo'))
|
||||||
|
self.assertFalse(self.keeper.set_is_member('test_set', 'bar'))
|
||||||
|
self.keeper.set_remove('test_set', 'foo')
|
||||||
|
self.assertFalse(self.keeper.set_is_member('test_set', 'foo'))
|
||||||
|
rv = self.keeper.set_fetch('test_set')
|
||||||
|
self.assertEqual(test_dict, rv.next())
|
||||||
|
self.keeper.set_remove('test_set', test_dict)
|
||||||
|
|
113
nova/tests/network_unittest.py
Normal file
113
nova/tests/network_unittest.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 unittest
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import IPy
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import test
|
||||||
|
from nova.compute import network
|
||||||
|
from nova.auth import users
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkTestCase(test.TrialTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(NetworkTestCase, self).setUp()
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
self.manager = users.UserManager.instance()
|
||||||
|
for i in range(0, 6):
|
||||||
|
name = 'user%s' % i
|
||||||
|
if not self.manager.get_user(name):
|
||||||
|
self.manager.create_user(name, name, name)
|
||||||
|
self.network = network.NetworkController(netsize=16)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(NetworkTestCase, self).tearDown()
|
||||||
|
for i in range(0, 6):
|
||||||
|
name = 'user%s' % i
|
||||||
|
self.manager.delete_user(name)
|
||||||
|
|
||||||
|
def test_network_serialization(self):
|
||||||
|
net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None)
|
||||||
|
address = net1.allocate_ip("user0", "01:24:55:36:f2:a0")
|
||||||
|
net_json = str(net1)
|
||||||
|
net2 = network.Network.from_json(net_json)
|
||||||
|
self.assertEqual(net_json, str(net2))
|
||||||
|
self.assertTrue(IPy.IP(address) in net2.network)
|
||||||
|
|
||||||
|
def test_allocate_deallocate_address(self):
|
||||||
|
for flag in flags.FLAGS:
|
||||||
|
print "%s=%s" % (flag, flags.FLAGS.get(flag, None))
|
||||||
|
(address, net_name) = self.network.allocate_address(
|
||||||
|
"user0", "01:24:55:36:f2:a0")
|
||||||
|
logging.debug("Was allocated %s" % (address))
|
||||||
|
self.assertEqual(True, address in self._get_user_addresses("user0"))
|
||||||
|
rv = self.network.deallocate_address(address)
|
||||||
|
self.assertEqual(False, address in self._get_user_addresses("user0"))
|
||||||
|
|
||||||
|
def test_range_allocation(self):
|
||||||
|
(address, net_name) = self.network.allocate_address(
|
||||||
|
"user0", "01:24:55:36:f2:a0")
|
||||||
|
(secondaddress, net_name) = self.network.allocate_address(
|
||||||
|
"user1", "01:24:55:36:f2:a0")
|
||||||
|
self.assertEqual(True, address in self._get_user_addresses("user0"))
|
||||||
|
self.assertEqual(True,
|
||||||
|
secondaddress in self._get_user_addresses("user1"))
|
||||||
|
self.assertEqual(False, address in self._get_user_addresses("user1"))
|
||||||
|
rv = self.network.deallocate_address(address)
|
||||||
|
self.assertEqual(False, address in self._get_user_addresses("user0"))
|
||||||
|
rv = self.network.deallocate_address(secondaddress)
|
||||||
|
self.assertEqual(False,
|
||||||
|
secondaddress in self._get_user_addresses("user1"))
|
||||||
|
|
||||||
|
def test_subnet_edge(self):
|
||||||
|
(secondaddress, net_name) = self.network.allocate_address("user0")
|
||||||
|
for user in range(1,5):
|
||||||
|
user_id = "user%s" % (user)
|
||||||
|
(address, net_name) = self.network.allocate_address(
|
||||||
|
user_id, "01:24:55:36:f2:a0")
|
||||||
|
(address2, net_name) = self.network.allocate_address(
|
||||||
|
user_id, "01:24:55:36:f2:a0")
|
||||||
|
(address3, net_name) = self.network.allocate_address(
|
||||||
|
user_id, "01:24:55:36:f2:a0")
|
||||||
|
self.assertEqual(False,
|
||||||
|
address in self._get_user_addresses("user0"))
|
||||||
|
self.assertEqual(False,
|
||||||
|
address2 in self._get_user_addresses("user0"))
|
||||||
|
self.assertEqual(False,
|
||||||
|
address3 in self._get_user_addresses("user0"))
|
||||||
|
rv = self.network.deallocate_address(address)
|
||||||
|
rv = self.network.deallocate_address(address2)
|
||||||
|
rv = self.network.deallocate_address(address3)
|
||||||
|
rv = self.network.deallocate_address(secondaddress)
|
||||||
|
|
||||||
|
def test_too_many_users(self):
|
||||||
|
for i in range(0, 30):
|
||||||
|
name = 'toomany-user%s' % i
|
||||||
|
self.manager.create_user(name, name, name)
|
||||||
|
(address, net_name) = self.network.allocate_address(
|
||||||
|
name, "01:24:55:36:f2:a0")
|
||||||
|
self.manager.delete_user(name)
|
||||||
|
|
||||||
|
def _get_user_addresses(self, user_id):
|
||||||
|
rv = self.network.describe_addresses()
|
||||||
|
user_addresses = []
|
||||||
|
for item in rv:
|
||||||
|
if item['user_id'] == user_id:
|
||||||
|
user_addresses.append(item['address'])
|
||||||
|
return user_addresses
|
128
nova/tests/node_unittest.py
Normal file
128
nova/tests/node_unittest.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 StringIO
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import mox
|
||||||
|
from tornado import ioloop
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import test
|
||||||
|
from nova import utils
|
||||||
|
from nova.compute import model
|
||||||
|
from nova.compute import node
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceXmlTestCase(test.TrialTestCase):
|
||||||
|
# @defer.inlineCallbacks
|
||||||
|
def test_serialization(self):
|
||||||
|
# TODO: Reimplement this, it doesn't make sense in redis-land
|
||||||
|
return
|
||||||
|
|
||||||
|
# instance_id = 'foo'
|
||||||
|
# first_node = node.Node()
|
||||||
|
# inst = yield first_node.run_instance(instance_id)
|
||||||
|
#
|
||||||
|
# # force the state so that we can verify that it changes
|
||||||
|
# inst._s['state'] = node.Instance.NOSTATE
|
||||||
|
# xml = inst.toXml()
|
||||||
|
# self.assert_(ElementTree.parse(StringIO.StringIO(xml)))
|
||||||
|
#
|
||||||
|
# second_node = node.Node()
|
||||||
|
# new_inst = node.Instance.fromXml(second_node._conn, pool=second_node._pool, xml=xml)
|
||||||
|
# self.assertEqual(new_inst.state, node.Instance.RUNNING)
|
||||||
|
# rv = yield first_node.terminate_instance(instance_id)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeConnectionTestCase(test.TrialTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
super(NodeConnectionTestCase, self).setUp()
|
||||||
|
self.flags(fake_libvirt=True,
|
||||||
|
fake_storage=True,
|
||||||
|
fake_users=True,
|
||||||
|
redis_db=8)
|
||||||
|
self.node = node.Node()
|
||||||
|
|
||||||
|
def create_instance(self):
|
||||||
|
instdir = model.InstanceDirectory()
|
||||||
|
inst = instdir.new()
|
||||||
|
# TODO(ja): add ami, ari, aki, user_data
|
||||||
|
inst['reservation_id'] = 'r-fakeres'
|
||||||
|
inst['launch_time'] = '10'
|
||||||
|
inst['owner_id'] = 'fake'
|
||||||
|
inst['node_name'] = FLAGS.node_name
|
||||||
|
inst['mac_address'] = utils.generate_mac()
|
||||||
|
inst['ami_launch_index'] = 0
|
||||||
|
inst.save()
|
||||||
|
return inst['instance_id']
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_run_describe_terminate(self):
|
||||||
|
instance_id = self.create_instance()
|
||||||
|
|
||||||
|
rv = yield self.node.run_instance(instance_id)
|
||||||
|
|
||||||
|
rv = yield self.node.describe_instances()
|
||||||
|
self.assertEqual(rv[instance_id].name, instance_id)
|
||||||
|
|
||||||
|
rv = yield self.node.terminate_instance(instance_id)
|
||||||
|
|
||||||
|
rv = yield self.node.describe_instances()
|
||||||
|
self.assertEqual(rv, {})
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_reboot(self):
|
||||||
|
instance_id = self.create_instance()
|
||||||
|
rv = yield self.node.run_instance(instance_id)
|
||||||
|
|
||||||
|
rv = yield self.node.describe_instances()
|
||||||
|
logging.debug("describe_instances returns %s" % (rv))
|
||||||
|
self.assertEqual(rv[instance_id].name, instance_id)
|
||||||
|
|
||||||
|
yield self.node.reboot_instance(instance_id)
|
||||||
|
|
||||||
|
rv = yield self.node.describe_instances()
|
||||||
|
self.assertEqual(rv[instance_id].name, instance_id)
|
||||||
|
rv = yield self.node.terminate_instance(instance_id)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_console_output(self):
|
||||||
|
instance_id = self.create_instance()
|
||||||
|
rv = yield self.node.run_instance(instance_id)
|
||||||
|
|
||||||
|
console = yield self.node.get_console_output(instance_id)
|
||||||
|
self.assert_(console)
|
||||||
|
rv = yield self.node.terminate_instance(instance_id)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_run_instance_existing(self):
|
||||||
|
instance_id = self.create_instance()
|
||||||
|
rv = yield self.node.run_instance(instance_id)
|
||||||
|
|
||||||
|
rv = yield self.node.describe_instances()
|
||||||
|
self.assertEqual(rv[instance_id].name, instance_id)
|
||||||
|
|
||||||
|
self.assertRaises(exception.Error, self.node.run_instance, instance_id)
|
||||||
|
rv = yield self.node.terminate_instance(instance_id)
|
190
nova/tests/objectstore_unittest.py
Normal file
190
nova/tests/objectstore_unittest.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 glob
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import rpc
|
||||||
|
from nova import objectstore
|
||||||
|
from nova import test
|
||||||
|
from nova.auth import users
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
oss_tempdir = tempfile.mkdtemp(prefix='test_oss-')
|
||||||
|
|
||||||
|
|
||||||
|
# delete tempdirs from previous runs (we don't delete after test to allow
|
||||||
|
# checking the contents after running tests)
|
||||||
|
for path in glob.glob(os.path.abspath(os.path.join(oss_tempdir, '../test_oss-*'))):
|
||||||
|
if path != oss_tempdir:
|
||||||
|
shutil.rmtree(path)
|
||||||
|
|
||||||
|
|
||||||
|
# create bucket/images path
|
||||||
|
os.makedirs(os.path.join(oss_tempdir, 'images'))
|
||||||
|
os.makedirs(os.path.join(oss_tempdir, 'buckets'))
|
||||||
|
|
||||||
|
class ObjectStoreTestCase(test.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(ObjectStoreTestCase, self).setUp()
|
||||||
|
self.flags(fake_users=True,
|
||||||
|
buckets_path=os.path.join(oss_tempdir, 'buckets'),
|
||||||
|
images_path=os.path.join(oss_tempdir, 'images'),
|
||||||
|
ca_path=os.path.join(os.path.dirname(__file__), 'CA'))
|
||||||
|
self.conn = rpc.Connection.instance()
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
self.um = users.UserManager.instance()
|
||||||
|
|
||||||
|
def test_buckets(self):
|
||||||
|
try:
|
||||||
|
self.um.create_user('user1')
|
||||||
|
except: pass
|
||||||
|
try:
|
||||||
|
self.um.create_user('user2')
|
||||||
|
except: pass
|
||||||
|
try:
|
||||||
|
self.um.create_user('admin_user', admin=True)
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
objectstore.bucket.Bucket.create('new_bucket', self.um.get_user('user1'))
|
||||||
|
bucket = objectstore.bucket.Bucket('new_bucket')
|
||||||
|
|
||||||
|
# creator is authorized to use bucket
|
||||||
|
self.assert_(bucket.is_authorized(self.um.get_user('user1')))
|
||||||
|
|
||||||
|
# another user is not authorized
|
||||||
|
self.assert_(bucket.is_authorized(self.um.get_user('user2')) == False)
|
||||||
|
|
||||||
|
# admin is authorized to use bucket
|
||||||
|
self.assert_(bucket.is_authorized(self.um.get_user('admin_user')))
|
||||||
|
|
||||||
|
# new buckets are empty
|
||||||
|
self.assert_(bucket.list_keys()['Contents'] == [])
|
||||||
|
|
||||||
|
# storing keys works
|
||||||
|
bucket['foo'] = "bar"
|
||||||
|
|
||||||
|
self.assert_(len(bucket.list_keys()['Contents']) == 1)
|
||||||
|
|
||||||
|
self.assert_(bucket['foo'].read() == 'bar')
|
||||||
|
|
||||||
|
# md5 of key works
|
||||||
|
self.assert_(bucket['foo'].md5 == hashlib.md5('bar').hexdigest())
|
||||||
|
|
||||||
|
# deleting non-empty bucket throws exception
|
||||||
|
exception = False
|
||||||
|
try:
|
||||||
|
bucket.delete()
|
||||||
|
except:
|
||||||
|
exception = True
|
||||||
|
|
||||||
|
self.assert_(exception)
|
||||||
|
|
||||||
|
# deleting key
|
||||||
|
del bucket['foo']
|
||||||
|
|
||||||
|
# deleting empty button
|
||||||
|
bucket.delete()
|
||||||
|
|
||||||
|
# accessing deleted bucket throws exception
|
||||||
|
exception = False
|
||||||
|
try:
|
||||||
|
objectstore.bucket.Bucket('new_bucket')
|
||||||
|
except:
|
||||||
|
exception = True
|
||||||
|
|
||||||
|
self.assert_(exception)
|
||||||
|
self.um.delete_user('user1')
|
||||||
|
self.um.delete_user('user2')
|
||||||
|
self.um.delete_user('admin_user')
|
||||||
|
|
||||||
|
def test_images(self):
|
||||||
|
try:
|
||||||
|
self.um.create_user('image_creator')
|
||||||
|
except: pass
|
||||||
|
image_user = self.um.get_user('image_creator')
|
||||||
|
|
||||||
|
# create a bucket for our bundle
|
||||||
|
objectstore.bucket.Bucket.create('image_bucket', image_user)
|
||||||
|
bucket = objectstore.bucket.Bucket('image_bucket')
|
||||||
|
|
||||||
|
# upload an image manifest/parts
|
||||||
|
bundle_path = os.path.join(os.path.dirname(__file__), 'bundle')
|
||||||
|
for path in glob.glob(bundle_path + '/*'):
|
||||||
|
bucket[os.path.basename(path)] = open(path, 'rb').read()
|
||||||
|
|
||||||
|
# register an image
|
||||||
|
objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', image_user)
|
||||||
|
|
||||||
|
# verify image
|
||||||
|
my_img = objectstore.image.Image('i-testing')
|
||||||
|
result_image_file = os.path.join(my_img.path, 'image')
|
||||||
|
self.assertEqual(os.stat(result_image_file).st_size, 1048576)
|
||||||
|
|
||||||
|
sha = hashlib.sha1(open(result_image_file).read()).hexdigest()
|
||||||
|
self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3')
|
||||||
|
|
||||||
|
# verify image permissions
|
||||||
|
try:
|
||||||
|
self.um.create_user('new_user')
|
||||||
|
except: pass
|
||||||
|
new_user = self.um.get_user('new_user')
|
||||||
|
self.assert_(my_img.is_authorized(new_user) == False)
|
||||||
|
|
||||||
|
self.um.delete_user('new_user')
|
||||||
|
self.um.delete_user('image_creator')
|
||||||
|
|
||||||
|
# class ApiObjectStoreTestCase(test.BaseTestCase):
|
||||||
|
# def setUp(self):
|
||||||
|
# super(ApiObjectStoreTestCase, self).setUp()
|
||||||
|
# FLAGS.fake_users = True
|
||||||
|
# FLAGS.buckets_path = os.path.join(tempdir, 'buckets')
|
||||||
|
# FLAGS.images_path = os.path.join(tempdir, 'images')
|
||||||
|
# FLAGS.ca_path = os.path.join(os.path.dirname(__file__), 'CA')
|
||||||
|
#
|
||||||
|
# self.users = users.UserManager.instance()
|
||||||
|
# self.app = handler.Application(self.users)
|
||||||
|
#
|
||||||
|
# self.host = '127.0.0.1'
|
||||||
|
#
|
||||||
|
# self.conn = boto.s3.connection.S3Connection(
|
||||||
|
# aws_access_key_id=user.access,
|
||||||
|
# aws_secret_access_key=user.secret,
|
||||||
|
# is_secure=False,
|
||||||
|
# calling_format=boto.s3.connection.OrdinaryCallingFormat(),
|
||||||
|
# port=FLAGS.s3_port,
|
||||||
|
# host=FLAGS.s3_host)
|
||||||
|
#
|
||||||
|
# self.mox.StubOutWithMock(self.ec2, 'new_http_connection')
|
||||||
|
#
|
||||||
|
# def tearDown(self):
|
||||||
|
# FLAGS.Reset()
|
||||||
|
# super(ApiObjectStoreTestCase, self).tearDown()
|
||||||
|
#
|
||||||
|
# def test_describe_instances(self):
|
||||||
|
# self.expect_http()
|
||||||
|
# self.mox.ReplayAll()
|
||||||
|
#
|
||||||
|
# self.assertEqual(self.ec2.get_all_instances(), [])
|
24
nova/tests/real_flags.py
Normal file
24
nova/tests/real_flags.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 nova import flags
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
FLAGS.fake_libvirt = False
|
||||||
|
FLAGS.fake_storage = False
|
||||||
|
FLAGS.fake_rabbit = False
|
||||||
|
FLAGS.fake_network = False
|
||||||
|
FLAGS.fake_users = False
|
||||||
|
FLAGS.verbose = False
|
86
nova/tests/storage_unittest.py
Normal file
86
nova/tests/storage_unittest.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 StringIO
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
import mox
|
||||||
|
from tornado import ioloop
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import test
|
||||||
|
from nova.compute import node
|
||||||
|
from nova.volume import storage
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class StorageTestCase(test.TrialTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
super(StorageTestCase, self).setUp()
|
||||||
|
self.mynode = node.Node()
|
||||||
|
self.mystorage = None
|
||||||
|
self.flags(fake_libvirt=True,
|
||||||
|
fake_storage=True,
|
||||||
|
redis_db=8)
|
||||||
|
if FLAGS.fake_storage:
|
||||||
|
self.mystorage = storage.FakeBlockStore()
|
||||||
|
else:
|
||||||
|
self.mystorage = storage.BlockStore()
|
||||||
|
|
||||||
|
@test.skip_if_fake
|
||||||
|
def test_run_create_volume(self):
|
||||||
|
vol_size = '0'
|
||||||
|
user_id = 'fake'
|
||||||
|
volume_id = self.mystorage.create_volume(vol_size, user_id)
|
||||||
|
# rv = self.mystorage.describe_volumes()
|
||||||
|
|
||||||
|
# Volumes have to be sorted by timestamp in order to work here...
|
||||||
|
# TODO(termie): get_volume returns differently than create_volume
|
||||||
|
self.assertEqual(volume_id,
|
||||||
|
self.mystorage.get_volume(volume_id)['volume_id'])
|
||||||
|
|
||||||
|
rv = self.mystorage.delete_volume(volume_id)
|
||||||
|
self.assertRaises(exception.Error,
|
||||||
|
self.mystorage.get_volume,
|
||||||
|
volume_id)
|
||||||
|
|
||||||
|
@test.skip_if_fake
|
||||||
|
def test_run_attach_detach_volume(self):
|
||||||
|
# Create one volume and one node to test with
|
||||||
|
instance_id = "storage-test"
|
||||||
|
# TODO(joshua) - Redo this test, can't make fake instances this way any more
|
||||||
|
# rv = self.mynode.run_instance(instance_id)
|
||||||
|
vol_size = "5"
|
||||||
|
user_id = "fake"
|
||||||
|
volume_id = self.mystorage.create_volume(vol_size, user_id)
|
||||||
|
rv = self.mystorage.attach_volume(volume_id,
|
||||||
|
instance_id,
|
||||||
|
"/dev/sdf")
|
||||||
|
volume_obj = self.mystorage.get_volume(volume_id)
|
||||||
|
self.assertEqual(volume_obj['status'], "attached")
|
||||||
|
# TODO(???): assert that it's attached to the right instance
|
||||||
|
|
||||||
|
rv = self.mystorage.detach_volume(volume_id)
|
||||||
|
volume_obj = self.mystorage.get_volume(volume_id)
|
||||||
|
self.assertEqual(volume_obj['status'], "available")
|
137
nova/tests/users_unittest.py
Normal file
137
nova/tests/users_unittest.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 unittest
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from M2Crypto import BIO
|
||||||
|
from M2Crypto import RSA
|
||||||
|
from M2Crypto import X509
|
||||||
|
|
||||||
|
from nova import crypto
|
||||||
|
from nova import flags
|
||||||
|
from nova import test
|
||||||
|
from nova import utils
|
||||||
|
from nova.auth import users
|
||||||
|
from nova.endpoint import cloud
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class UserTestCase(test.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(UserTestCase, self).setUp()
|
||||||
|
self.flags(fake_libvirt=True,
|
||||||
|
fake_storage=True,
|
||||||
|
redis_db=8)
|
||||||
|
self.users = users.UserManager.instance()
|
||||||
|
|
||||||
|
def test_001_can_create_user(self):
|
||||||
|
self.users.create_user('test1', 'access', 'secret')
|
||||||
|
|
||||||
|
def test_002_can_get_user(self):
|
||||||
|
user = self.users.get_user('test1')
|
||||||
|
|
||||||
|
def test_003_can_retreive_properties(self):
|
||||||
|
user = self.users.get_user('test1')
|
||||||
|
self.assertEqual('test1', user.id)
|
||||||
|
self.assertEqual('access', user.access)
|
||||||
|
self.assertEqual('secret', user.secret)
|
||||||
|
|
||||||
|
def test_004_signature_is_valid(self):
|
||||||
|
#self.assertTrue(self.users.authenticate( **boto.generate_url ... ? ? ? ))
|
||||||
|
pass
|
||||||
|
#raise NotImplementedError
|
||||||
|
|
||||||
|
def test_005_can_get_credentials(self):
|
||||||
|
return
|
||||||
|
credentials = self.users.get_user('test1').get_credentials()
|
||||||
|
self.assertEqual(credentials,
|
||||||
|
'export EC2_ACCESS_KEY="access"\n' +
|
||||||
|
'export EC2_SECRET_KEY="secret"\n' +
|
||||||
|
'export EC2_URL="http://127.0.0.1:8773/services/Cloud"\n' +
|
||||||
|
'export S3_URL="http://127.0.0.1:3333/"\n' +
|
||||||
|
'export EC2_USER_ID="test1"\n')
|
||||||
|
|
||||||
|
def test_006_test_key_storage(self):
|
||||||
|
user = self.users.get_user('test1')
|
||||||
|
user.create_key_pair('public', 'key', 'fingerprint')
|
||||||
|
key = user.get_key_pair('public')
|
||||||
|
self.assertEqual('key', key.public_key)
|
||||||
|
self.assertEqual('fingerprint', key.fingerprint)
|
||||||
|
|
||||||
|
def test_007_test_key_generation(self):
|
||||||
|
user = self.users.get_user('test1')
|
||||||
|
private_key, fingerprint = user.generate_key_pair('public2')
|
||||||
|
key = RSA.load_key_string(private_key, callback=lambda: None)
|
||||||
|
bio = BIO.MemoryBuffer()
|
||||||
|
public_key = user.get_key_pair('public2').public_key
|
||||||
|
key.save_pub_key_bio(bio)
|
||||||
|
converted = crypto.ssl_pub_to_ssh_pub(bio.read())
|
||||||
|
# assert key fields are equal
|
||||||
|
print converted
|
||||||
|
self.assertEqual(public_key.split(" ")[1].strip(),
|
||||||
|
converted.split(" ")[1].strip())
|
||||||
|
|
||||||
|
def test_008_can_list_key_pairs(self):
|
||||||
|
keys = self.users.get_user('test1').get_key_pairs()
|
||||||
|
self.assertTrue(filter(lambda k: k.name == 'public', keys))
|
||||||
|
self.assertTrue(filter(lambda k: k.name == 'public2', keys))
|
||||||
|
|
||||||
|
def test_009_can_delete_key_pair(self):
|
||||||
|
self.users.get_user('test1').delete_key_pair('public')
|
||||||
|
keys = self.users.get_user('test1').get_key_pairs()
|
||||||
|
self.assertFalse(filter(lambda k: k.name == 'public', keys))
|
||||||
|
|
||||||
|
def test_010_can_list_users(self):
|
||||||
|
users = self.users.get_users()
|
||||||
|
self.assertTrue(filter(lambda u: u.id == 'test1', users))
|
||||||
|
|
||||||
|
def test_011_can_generate_x509(self):
|
||||||
|
# MUST HAVE RUN CLOUD SETUP BY NOW
|
||||||
|
self.cloud = cloud.CloudController()
|
||||||
|
self.cloud.setup()
|
||||||
|
private_key, signed_cert_string = self.users.get_user('test1').generate_x509_cert()
|
||||||
|
logging.debug(signed_cert_string)
|
||||||
|
|
||||||
|
# Need to verify that it's signed by the right intermediate CA
|
||||||
|
full_chain = crypto.fetch_ca(username='test1', chain=True)
|
||||||
|
int_cert = crypto.fetch_ca(username='test1', chain=False)
|
||||||
|
cloud_cert = crypto.fetch_ca()
|
||||||
|
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
|
||||||
|
signed_cert = X509.load_cert_string(signed_cert_string)
|
||||||
|
chain_cert = X509.load_cert_string(full_chain)
|
||||||
|
int_cert = X509.load_cert_string(int_cert)
|
||||||
|
cloud_cert = X509.load_cert_string(cloud_cert)
|
||||||
|
self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
|
||||||
|
self.assertTrue(signed_cert.verify(int_cert.get_pubkey()))
|
||||||
|
|
||||||
|
if not FLAGS.use_intermediate_ca:
|
||||||
|
self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
|
||||||
|
else:
|
||||||
|
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
|
||||||
|
|
||||||
|
def test_012_can_delete_user(self):
|
||||||
|
self.users.delete_user('test1')
|
||||||
|
users = self.users.get_users()
|
||||||
|
if users != None:
|
||||||
|
self.assertFalse(filter(lambda u: u.id == 'test1', users))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# TODO: Implement use_fake as an option
|
||||||
|
unittest.main()
|
249
nova/twistd.py
Normal file
249
nova/twistd.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Twisted daemon helpers, specifically to parse out gFlags from twisted flags,
|
||||||
|
manage pid files and support syslogging.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import UserDict
|
||||||
|
import logging.handlers
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from twisted.scripts import twistd
|
||||||
|
from twisted.python import log
|
||||||
|
from twisted.python import reflect
|
||||||
|
from twisted.python import runtime
|
||||||
|
from twisted.python import usage
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
|
||||||
|
if runtime.platformType == "win32":
|
||||||
|
from twisted.scripts._twistw import ServerOptions
|
||||||
|
else:
|
||||||
|
from twisted.scripts._twistd_unix import ServerOptions
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class TwistdServerOptions(ServerOptions):
|
||||||
|
def parseArgs(self, *args):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def WrapTwistedOptions(wrapped):
|
||||||
|
class TwistedOptionsToFlags(wrapped):
|
||||||
|
subCommands = None
|
||||||
|
def __init__(self):
|
||||||
|
# NOTE(termie): _data exists because Twisted stuff expects
|
||||||
|
# to be able to set arbitrary things that are
|
||||||
|
# not actual flags
|
||||||
|
self._data = {}
|
||||||
|
self._flagHandlers = {}
|
||||||
|
self._paramHandlers = {}
|
||||||
|
|
||||||
|
# Absorb the twistd flags into our FLAGS
|
||||||
|
self._absorbFlags()
|
||||||
|
self._absorbParameters()
|
||||||
|
self._absorbHandlers()
|
||||||
|
|
||||||
|
super(TwistedOptionsToFlags, self).__init__()
|
||||||
|
|
||||||
|
def _absorbFlags(self):
|
||||||
|
twistd_flags = []
|
||||||
|
reflect.accumulateClassList(self.__class__, 'optFlags', twistd_flags)
|
||||||
|
for flag in twistd_flags:
|
||||||
|
key = flag[0].replace('-', '_')
|
||||||
|
flags.DEFINE_boolean(key, None, str(flag[-1]))
|
||||||
|
|
||||||
|
def _absorbParameters(self):
|
||||||
|
twistd_params = []
|
||||||
|
reflect.accumulateClassList(self.__class__, 'optParameters', twistd_params)
|
||||||
|
for param in twistd_params:
|
||||||
|
key = param[0].replace('-', '_')
|
||||||
|
flags.DEFINE_string(key, param[2], str(param[-1]))
|
||||||
|
|
||||||
|
def _absorbHandlers(self):
|
||||||
|
twistd_handlers = {}
|
||||||
|
reflect.addMethodNamesToDict(self.__class__, twistd_handlers, "opt_")
|
||||||
|
|
||||||
|
# NOTE(termie): Much of the following is derived/copied from
|
||||||
|
# twisted.python.usage with the express purpose of
|
||||||
|
# providing compatibility
|
||||||
|
for name in twistd_handlers.keys():
|
||||||
|
method = getattr(self, 'opt_'+name)
|
||||||
|
|
||||||
|
takesArg = not usage.flagFunction(method, name)
|
||||||
|
doc = getattr(method, '__doc__', None)
|
||||||
|
if not doc:
|
||||||
|
doc = 'undocumented'
|
||||||
|
|
||||||
|
if not takesArg:
|
||||||
|
if name not in FLAGS:
|
||||||
|
flags.DEFINE_boolean(name, None, doc)
|
||||||
|
self._flagHandlers[name] = method
|
||||||
|
else:
|
||||||
|
if name not in FLAGS:
|
||||||
|
flags.DEFINE_string(name, None, doc)
|
||||||
|
self._paramHandlers[name] = method
|
||||||
|
|
||||||
|
|
||||||
|
def _doHandlers(self):
|
||||||
|
for flag, handler in self._flagHandlers.iteritems():
|
||||||
|
if self[flag]:
|
||||||
|
handler()
|
||||||
|
for param, handler in self._paramHandlers.iteritems():
|
||||||
|
if self[param] is not None:
|
||||||
|
handler(self[param])
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(FLAGS)
|
||||||
|
|
||||||
|
def parseOptions(self, options=None):
|
||||||
|
if options is None:
|
||||||
|
options = sys.argv
|
||||||
|
else:
|
||||||
|
options.insert(0, '')
|
||||||
|
|
||||||
|
args = FLAGS(options)
|
||||||
|
argv = args[1:]
|
||||||
|
# ignore subcommands
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.parseArgs(*argv)
|
||||||
|
except TypeError:
|
||||||
|
raise usage.UsageError("Wrong number of arguments.")
|
||||||
|
|
||||||
|
self.postOptions()
|
||||||
|
return args
|
||||||
|
|
||||||
|
def parseArgs(self, *args):
|
||||||
|
# TODO(termie): figure out a decent way of dealing with args
|
||||||
|
#return
|
||||||
|
super(TwistedOptionsToFlags, self).parseArgs(*args)
|
||||||
|
|
||||||
|
def postOptions(self):
|
||||||
|
self._doHandlers()
|
||||||
|
|
||||||
|
super(TwistedOptionsToFlags, self).postOptions()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
key = key.replace('-', '_')
|
||||||
|
try:
|
||||||
|
return getattr(FLAGS, key)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
return self._data[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
key = key.replace('-', '_')
|
||||||
|
try:
|
||||||
|
return setattr(FLAGS, key, value)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
self._data[key] = value
|
||||||
|
|
||||||
|
return TwistedOptionsToFlags
|
||||||
|
|
||||||
|
|
||||||
|
def stop(pidfile):
|
||||||
|
"""
|
||||||
|
Stop the daemon
|
||||||
|
"""
|
||||||
|
# Get the pid from the pidfile
|
||||||
|
try:
|
||||||
|
pf = file(pidfile,'r')
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
pf.close()
|
||||||
|
except IOError:
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
if not pid:
|
||||||
|
message = "pidfile %s does not exist. Daemon not running?\n"
|
||||||
|
sys.stderr.write(message % pidfile)
|
||||||
|
return # not an error in a restart
|
||||||
|
|
||||||
|
# Try killing the daemon process
|
||||||
|
try:
|
||||||
|
while 1:
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
time.sleep(0.1)
|
||||||
|
except OSError, err:
|
||||||
|
err = str(err)
|
||||||
|
if err.find("No such process") > 0:
|
||||||
|
if os.path.exists(pidfile):
|
||||||
|
os.remove(pidfile)
|
||||||
|
else:
|
||||||
|
print str(err)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def serve(filename):
|
||||||
|
logging.debug("Serving %s" % filename)
|
||||||
|
name = os.path.basename(filename)
|
||||||
|
OptionsClass = WrapTwistedOptions(TwistdServerOptions)
|
||||||
|
options = OptionsClass()
|
||||||
|
argv = options.parseOptions()
|
||||||
|
logging.getLogger('amqplib').setLevel(logging.WARN)
|
||||||
|
FLAGS.python = filename
|
||||||
|
FLAGS.no_save = True
|
||||||
|
if not FLAGS.pidfile:
|
||||||
|
FLAGS.pidfile = '%s.pid' % name
|
||||||
|
elif FLAGS.pidfile.endswith('twistd.pid'):
|
||||||
|
FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name)
|
||||||
|
|
||||||
|
if not FLAGS.logfile:
|
||||||
|
FLAGS.logfile = '%s.log' % name
|
||||||
|
|
||||||
|
action = 'start'
|
||||||
|
if len(argv) > 1:
|
||||||
|
action = argv.pop()
|
||||||
|
|
||||||
|
if action == 'stop':
|
||||||
|
stop(FLAGS.pidfile)
|
||||||
|
sys.exit()
|
||||||
|
elif action == 'restart':
|
||||||
|
stop(FLAGS.pidfile)
|
||||||
|
elif action == 'start':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print 'usage: %s [options] [start|stop|restart]' % argv[0]
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
name + '(%(name)s): %(levelname)s %(message)s')
|
||||||
|
handler = logging.StreamHandler(log.StdioOnnaStick())
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logging.getLogger().addHandler(handler)
|
||||||
|
|
||||||
|
if FLAGS.verbose:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
logging.getLogger().setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
if FLAGS.syslog:
|
||||||
|
syslog = logging.handlers.SysLogHandler(address='/dev/log')
|
||||||
|
syslog.setFormatter(formatter)
|
||||||
|
logging.getLogger().addHandler(syslog)
|
||||||
|
|
||||||
|
logging.debug("Full set of FLAGS:")
|
||||||
|
for flag in FLAGS:
|
||||||
|
logging.debug("%s : %s" % (flag, FLAGS.get(flag, None)))
|
||||||
|
|
||||||
|
twistd.runApp(options)
|
43
nova/vendor.py
Normal file
43
nova/vendor.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get our vendor folders into the system path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# abspath/__file__/../vendor
|
||||||
|
VENDOR_PATH = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vendor'))
|
||||||
|
|
||||||
|
if not os.path.exists(VENDOR_PATH):
|
||||||
|
print 'warning: no vendor libraries included'
|
||||||
|
else:
|
||||||
|
paths = [VENDOR_PATH,
|
||||||
|
os.path.join(VENDOR_PATH, 'pymox'),
|
||||||
|
os.path.join(VENDOR_PATH, 'tornado'),
|
||||||
|
os.path.join(VENDOR_PATH, 'python-gflags'),
|
||||||
|
os.path.join(VENDOR_PATH, 'python-daemon'),
|
||||||
|
os.path.join(VENDOR_PATH, 'lockfile'),
|
||||||
|
os.path.join(VENDOR_PATH, 'boto'),
|
||||||
|
os.path.join(VENDOR_PATH, 'Twisted-10.0.0'),
|
||||||
|
os.path.join(VENDOR_PATH, 'redis-py'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for p in paths:
|
||||||
|
if p not in sys.path:
|
||||||
|
sys.path.insert(0, p)
|
99
run_tests.py
Normal file
99
run_tests.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
# Copyright [2010] [Anso Labs, LLC]
|
||||||
|
#
|
||||||
|
# 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 is our basic test running framework based on Twisted's Trial.
|
||||||
|
|
||||||
|
Usage Examples:
|
||||||
|
|
||||||
|
# to run all the tests
|
||||||
|
python run_tests.py
|
||||||
|
|
||||||
|
# to run a specific test suite imported here
|
||||||
|
python run_tests.py NodeConnectionTestCase
|
||||||
|
|
||||||
|
# to run a specific test imported here
|
||||||
|
python run_tests.py NodeConnectionTestCase.test_reboot
|
||||||
|
|
||||||
|
# to run some test suites elsewhere
|
||||||
|
python run_tests.py nova.tests.node_unittest
|
||||||
|
python run_tests.py nova.tests.node_unittest.NodeConnectionTestCase
|
||||||
|
|
||||||
|
Due to our use of multiprocessing it we frequently get some ignorable
|
||||||
|
'Interrupted system call' exceptions after test completion.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import __main__
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from nova import vendor
|
||||||
|
from twisted.scripts import trial as trial_script
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import twistd
|
||||||
|
|
||||||
|
from nova.tests.access_unittest import *
|
||||||
|
from nova.tests.api_unittest import *
|
||||||
|
from nova.tests.cloud_unittest import *
|
||||||
|
from nova.tests.keeper_unittest import *
|
||||||
|
from nova.tests.network_unittest import *
|
||||||
|
from nova.tests.node_unittest import *
|
||||||
|
from nova.tests.objectstore_unittest import *
|
||||||
|
from nova.tests.storage_unittest import *
|
||||||
|
from nova.tests.users_unittest import *
|
||||||
|
from nova.tests.datastore_unittest import *
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
OptionsClass = twistd.WrapTwistedOptions(trial_script.Options)
|
||||||
|
config = OptionsClass()
|
||||||
|
argv = config.parseOptions()
|
||||||
|
|
||||||
|
FLAGS.verbose = True
|
||||||
|
|
||||||
|
# TODO(termie): these should make a call instead of doing work on import
|
||||||
|
if FLAGS.fake_tests:
|
||||||
|
from nova.tests.fake_flags import *
|
||||||
|
else:
|
||||||
|
from nova.tests.real_flags import *
|
||||||
|
|
||||||
|
if len(argv) == 1 and len(config['tests']) == 0:
|
||||||
|
# If no tests were specified run the ones imported in this file
|
||||||
|
# NOTE(termie): "tests" is not a flag, just some Trial related stuff
|
||||||
|
config['tests'].update(['__main__'])
|
||||||
|
elif len(config['tests']):
|
||||||
|
# If we specified tests check first whether they are in __main__
|
||||||
|
for arg in config['tests']:
|
||||||
|
key = arg.split('.')[0]
|
||||||
|
if hasattr(__main__, key):
|
||||||
|
config['tests'].remove(arg)
|
||||||
|
config['tests'].add('__main__.%s' % arg)
|
||||||
|
|
||||||
|
trial_script._initialDebugSetup(config)
|
||||||
|
trialRunner = trial_script._makeRunner(config)
|
||||||
|
suite = trial_script._getSuite(config)
|
||||||
|
if config['until-failure']:
|
||||||
|
test_result = trialRunner.runUntilFailure(suite)
|
||||||
|
else:
|
||||||
|
test_result = trialRunner.run(suite)
|
||||||
|
if config.tracer:
|
||||||
|
sys.settrace(None)
|
||||||
|
results = config.tracer.results()
|
||||||
|
results.write_results(show_missing=1, summary=False,
|
||||||
|
coverdir=config.coverdir)
|
||||||
|
sys.exit(not test_result.wasSuccessful())
|
1304
vendor/IPy.py
vendored
Normal file
1304
vendor/IPy.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
32
vendor/Twisted-10.0.0/INSTALL
vendored
Normal file
32
vendor/Twisted-10.0.0/INSTALL
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
Requirements
|
||||||
|
|
||||||
|
Python 2.4, 2.5 or 2.6.
|
||||||
|
|
||||||
|
Zope Interfaces 3.0.1 (http://zope.org/Products/ZopeInterface) - if
|
||||||
|
you have ZopeX3 (at least version 3.0.0c1) installed that should
|
||||||
|
work too.
|
||||||
|
|
||||||
|
On Windows pywin32 is recommended (this is built in to ActivePython,
|
||||||
|
so no need to reinstall if you use it instead of standard Python)
|
||||||
|
http://sourceforge.net/project/showfiles.php?group_id=78018
|
||||||
|
The Windows IOCP reactor requires pywin32 build 205 or later.
|
||||||
|
|
||||||
|
If you would like to use Trial's subunit reporter, then you will need to
|
||||||
|
install Subunit 0.0.2 or later (https://launchpad.net/subunit).
|
||||||
|
|
||||||
|
Installation
|
||||||
|
|
||||||
|
* Debian and Ubuntu
|
||||||
|
Packages are included in the main distribution.
|
||||||
|
|
||||||
|
* FreeBSD, Gentoo
|
||||||
|
Twisted is in their package repositories.
|
||||||
|
|
||||||
|
* Win32
|
||||||
|
EXEs are available from http://twistedmatrix.com/
|
||||||
|
|
||||||
|
* Other
|
||||||
|
As with other Python packages, the standard way of installing from source
|
||||||
|
is:
|
||||||
|
|
||||||
|
python setup.py install
|
57
vendor/Twisted-10.0.0/LICENSE
vendored
Normal file
57
vendor/Twisted-10.0.0/LICENSE
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
Copyright (c) 2001-2010
|
||||||
|
Allen Short
|
||||||
|
Andy Gayton
|
||||||
|
Andrew Bennetts
|
||||||
|
Antoine Pitrou
|
||||||
|
Apple Computer, Inc.
|
||||||
|
Benjamin Bruheim
|
||||||
|
Bob Ippolito
|
||||||
|
Canonical Limited
|
||||||
|
Christopher Armstrong
|
||||||
|
David Reid
|
||||||
|
Donovan Preston
|
||||||
|
Eric Mangold
|
||||||
|
Eyal Lotem
|
||||||
|
Itamar Shtull-Trauring
|
||||||
|
James Knight
|
||||||
|
Jason A. Mobarak
|
||||||
|
Jean-Paul Calderone
|
||||||
|
Jessica McKellar
|
||||||
|
Jonathan Jacobs
|
||||||
|
Jonathan Lange
|
||||||
|
Jonathan D. Simms
|
||||||
|
Jürgen Hermann
|
||||||
|
Kevin Horn
|
||||||
|
Kevin Turner
|
||||||
|
Mary Gardiner
|
||||||
|
Matthew Lefkowitz
|
||||||
|
Massachusetts Institute of Technology
|
||||||
|
Moshe Zadka
|
||||||
|
Paul Swartz
|
||||||
|
Pavel Pergamenshchik
|
||||||
|
Ralph Meijer
|
||||||
|
Sean Riley
|
||||||
|
Software Freedom Conservancy
|
||||||
|
Travis B. Hartwell
|
||||||
|
Thijs Triemstra
|
||||||
|
Thomas Herve
|
||||||
|
Timothy Allen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1416
vendor/Twisted-10.0.0/NEWS
vendored
Normal file
1416
vendor/Twisted-10.0.0/NEWS
vendored
Normal file
File diff suppressed because it is too large
Load Diff
118
vendor/Twisted-10.0.0/README
vendored
Normal file
118
vendor/Twisted-10.0.0/README
vendored
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
Twisted 10.0.0
|
||||||
|
|
||||||
|
Quote of the Release:
|
||||||
|
|
||||||
|
[on picking the quote of the release]
|
||||||
|
<glyph> Man, we're going to have to get a lot funnier if we're going
|
||||||
|
to do time-based releases
|
||||||
|
|
||||||
|
|
||||||
|
For information on what's new in Twisted 10.0.0, see the NEWS file that comes
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
What is this?
|
||||||
|
=============
|
||||||
|
|
||||||
|
Twisted is an event-based framework for internet applications which works on
|
||||||
|
Python 2.4 through 2.6. The following are some of the modules included
|
||||||
|
with Twisted::
|
||||||
|
|
||||||
|
- twisted.application
|
||||||
|
A "Service" system that allows you to organize your application in
|
||||||
|
hierarchies with well-defined startup and dependency semantics,
|
||||||
|
- twisted.cred
|
||||||
|
A general credentials and authentication system that facilitates
|
||||||
|
pluggable authentication backends,
|
||||||
|
- twisted.enterprise
|
||||||
|
Asynchronous database access, compatible with any Python DBAPI2.0
|
||||||
|
modules,
|
||||||
|
- twisted.internet
|
||||||
|
Low-level asynchronous networking APIs that allow you to define
|
||||||
|
your own protocols that run over certain transports,
|
||||||
|
- twisted.manhole
|
||||||
|
A tool for remote debugging of your services which gives you a
|
||||||
|
Python interactive interpreter,
|
||||||
|
- twisted.protocols
|
||||||
|
Basic protocol implementations and helpers for your own protocol
|
||||||
|
implementations,
|
||||||
|
- twisted.python
|
||||||
|
A large set of utilities for Python tricks, reflection, text
|
||||||
|
processing, and anything else,
|
||||||
|
- twisted.spread
|
||||||
|
A secure, fast remote object system,
|
||||||
|
- twisted.trial
|
||||||
|
A unit testing framework that integrates well with Twisted-based code.
|
||||||
|
|
||||||
|
Twisted supports integration of the Tk, GTK+, GTK+ 2, Qt, Mac OS X,
|
||||||
|
or wxPython event loop with its main event loop. The Win32 event
|
||||||
|
loop is also supported.
|
||||||
|
|
||||||
|
For more information, visit http://www.twistedmatrix.com, or join the list
|
||||||
|
at http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
|
||||||
|
|
||||||
|
There are many official Twisted subprojects, including clients and
|
||||||
|
servers for web, mail, DNS, and more. You can find out more about
|
||||||
|
these projects at http://twistedmatrix.com/trac/wiki/TwistedProjects
|
||||||
|
|
||||||
|
|
||||||
|
Installing
|
||||||
|
==========
|
||||||
|
|
||||||
|
Instructions for installing this software are in INSTALL.
|
||||||
|
|
||||||
|
Unit Tests
|
||||||
|
==========
|
||||||
|
|
||||||
|
|
||||||
|
See our unit tests run proving that the software is BugFree(TM)::
|
||||||
|
|
||||||
|
% trial twisted
|
||||||
|
|
||||||
|
Some of these tests may fail if you
|
||||||
|
* don't have the dependancies required for a particular subsystem installed,
|
||||||
|
* have a firewall blocking some ports (or things like Multicast, which Linux
|
||||||
|
NAT has shown itself to do), or
|
||||||
|
* run them as root.
|
||||||
|
|
||||||
|
|
||||||
|
Documentation and Support
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Examples on how to use Twisted APIs are located in doc/examples;
|
||||||
|
this might ease the learning curve a little bit, since all these
|
||||||
|
files are kept as short as possible. The file doc/howto/index.xhtml
|
||||||
|
contains an index of all the HOWTOs: this should be your starting
|
||||||
|
point when looking for documentation.
|
||||||
|
|
||||||
|
Help is available on the Twisted mailing list::
|
||||||
|
|
||||||
|
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
|
||||||
|
|
||||||
|
There is also a very lively IRC channel, #twisted, on
|
||||||
|
irc.freenode.net.
|
||||||
|
|
||||||
|
|
||||||
|
Copyright
|
||||||
|
=========
|
||||||
|
|
||||||
|
All of the code in this distribution is Copyright (c) 2001-2010
|
||||||
|
Twisted Matrix Laboratories.
|
||||||
|
|
||||||
|
Twisted is made available under the MIT license. The included
|
||||||
|
LICENSE file describes this in detail.
|
||||||
|
|
||||||
|
|
||||||
|
Warranty
|
||||||
|
========
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||||
|
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE USE OF THIS SOFTWARE IS WITH YOU.
|
||||||
|
|
||||||
|
IN NO EVENT WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||||
|
AND/OR REDISTRIBUTE THE LIBRARY, BE LIABLE TO YOU FOR ANY DAMAGES, EVEN IF
|
||||||
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGES.
|
||||||
|
|
||||||
|
Again, see the included LICENSE file for specific legal details.
|
BIN
vendor/Twisted-10.0.0/bin/.twistd.swp
vendored
Normal file
BIN
vendor/Twisted-10.0.0/bin/.twistd.swp
vendored
Normal file
Binary file not shown.
20
vendor/Twisted-10.0.0/bin/conch/cftp
vendored
Executable file
20
vendor/Twisted-10.0.0/bin/conch/cftp
vendored
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
|
||||||
|
# See LICENSE for details.
|
||||||
|
|
||||||
|
|
||||||
|
### Twisted Preamble
|
||||||
|
# This makes sure that users don't have to set up their environment
|
||||||
|
# specially in order to run these programs from bin/.
|
||||||
|
import sys, os
|
||||||
|
path = os.path.abspath(sys.argv[0])
|
||||||
|
while os.path.dirname(path) != path:
|
||||||
|
if os.path.basename(path).startswith('Twisted'):
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
break
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
### end of preamble
|
||||||
|
|
||||||
|
from twisted.conch.scripts.cftp import run
|
||||||
|
run()
|
20
vendor/Twisted-10.0.0/bin/conch/ckeygen
vendored
Executable file
20
vendor/Twisted-10.0.0/bin/conch/ckeygen
vendored
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
|
||||||
|
# See LICENSE for details.
|
||||||
|
|
||||||
|
|
||||||
|
### Twisted Preamble
|
||||||
|
# This makes sure that users don't have to set up their environment
|
||||||
|
# specially in order to run these programs from bin/.
|
||||||
|
import sys, os
|
||||||
|
path = os.path.abspath(sys.argv[0])
|
||||||
|
while os.path.dirname(path) != path:
|
||||||
|
if os.path.basename(path).startswith('Twisted'):
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
break
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
### end of preamble
|
||||||
|
|
||||||
|
from twisted.conch.scripts.ckeygen import run
|
||||||
|
run()
|
20
vendor/Twisted-10.0.0/bin/conch/conch
vendored
Executable file
20
vendor/Twisted-10.0.0/bin/conch/conch
vendored
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
|
||||||
|
# See LICENSE for details.
|
||||||
|
|
||||||
|
|
||||||
|
### Twisted Preamble
|
||||||
|
# This makes sure that users don't have to set up their environment
|
||||||
|
# specially in order to run these programs from bin/.
|
||||||
|
import sys, os
|
||||||
|
path = os.path.abspath(sys.argv[0])
|
||||||
|
while os.path.dirname(path) != path:
|
||||||
|
if os.path.basename(path).startswith('Twisted'):
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
break
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
### end of preamble
|
||||||
|
|
||||||
|
from twisted.conch.scripts.conch import run
|
||||||
|
run()
|
20
vendor/Twisted-10.0.0/bin/conch/tkconch
vendored
Executable file
20
vendor/Twisted-10.0.0/bin/conch/tkconch
vendored
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
|
||||||
|
# See LICENSE for details.
|
||||||
|
|
||||||
|
|
||||||
|
### Twisted Preamble
|
||||||
|
# This makes sure that users don't have to set up their environment
|
||||||
|
# specially in order to run these programs from bin/.
|
||||||
|
import sys, os
|
||||||
|
path = os.path.abspath(sys.argv[0])
|
||||||
|
while os.path.dirname(path) != path:
|
||||||
|
if os.path.basename(path).startswith('Twisted'):
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
break
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
### end of preamble
|
||||||
|
|
||||||
|
from twisted.conch.scripts.tkconch import run
|
||||||
|
run()
|
21
vendor/Twisted-10.0.0/bin/lore/lore
vendored
Executable file
21
vendor/Twisted-10.0.0/bin/lore/lore
vendored
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
|
||||||
|
# See LICENSE for details.
|
||||||
|
|
||||||
|
|
||||||
|
### Twisted Preamble
|
||||||
|
# This makes sure that users don't have to set up their environment
|
||||||
|
# specially in order to run these programs from bin/.
|
||||||
|
import sys, os
|
||||||
|
path = os.path.abspath(sys.argv[0])
|
||||||
|
while os.path.dirname(path) != path:
|
||||||
|
if os.path.basename(path).startswith('Twisted'):
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
break
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
### end of preamble
|
||||||
|
|
||||||
|
from twisted.lore.scripts.lore import run
|
||||||
|
run()
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user