Retire astara repo
Retire repository, following https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project Change-Id: If5f7d284bd107a93edd9272ac0ed8e6d20ba5c51changes/56/610356/1
parent
e7f8940fa0
commit
7759e2fd82
|
@ -1,53 +0,0 @@
|
|||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
||||
# Packaging output
|
||||
*.deb
|
||||
|
||||
# pbr output
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
orchestrator.ini.sample
|
||||
astara/test/functional/test.conf.sample
|
||||
|
||||
*.swp
|
||||
|
||||
#pycharm cruft
|
||||
.idea/*
|
||||
|
||||
*.db
|
||||
*.db_clean
|
||||
|
||||
#macos hidden files
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
|
||||
# Vagrant
|
||||
vagrant/.vagrant
|
||||
vagrant/user_local.conf
|
10
.travis.yml
10
.travis.yml
|
@ -1,10 +0,0 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
install:
|
||||
- pip install -r test_requirements.txt --use-mirrors
|
||||
- pip install flake8 --use-mirrors
|
||||
- pip install -q . --use-mirrors
|
||||
before_script:
|
||||
- flake8 setup.py akanda --ignore=E123,E133,E226,E241,E242,E731
|
||||
script: nosetests -d
|
175
LICENSE
175
LICENSE
|
@ -1,175 +0,0 @@
|
|||
|
||||
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.
|
70
README.md
70
README.md
|
@ -1,70 +0,0 @@
|
|||
# Astara
|
||||
|
||||
A service with an open plugin architecture that manages Neutron advanced
|
||||
services such as routers and load balancers within an OpenStack environment.
|
||||
|
||||
## The Name
|
||||
|
||||
Astara is the sanskrit word for carpet. So why name our project carpet?
|
||||
|
||||
The original code name for this project was simply "The RUG" which was a
|
||||
reference to a line from the popular film "The Big Lebowski":
|
||||
|
||||
**That rug really tied the room together, did it not?**
|
||||
|
||||
The idea is that "The Rug" really ties OpenStack neutron together nicely. We
|
||||
felt it was an apt description so we kept the name.
|
||||
|
||||
## Related Projects
|
||||
|
||||
The code for the Astara project lives in several separate repositories to ease
|
||||
packaging and management:
|
||||
|
||||
|
||||
* [Astara](https://github.com/openstack/astara) -
|
||||
Contains the Orchestration service for managing the creation, configuration,
|
||||
and health of neutron advanced services as virtual network functions.
|
||||
|
||||
* [Astara Appliance](https://github.com/openstack/astara-appliance) –
|
||||
Supporting software for the Astara virtual network appliance, which is
|
||||
a Linux-based service VM that provides routing and L3+ services in
|
||||
a virtualized network environment. This includes a REST API for managing
|
||||
the appliance via the Astara orchestration service.
|
||||
|
||||
* [Astara Neutron](https://github.com/openstack/astara-neutron) –
|
||||
Ancillary subclasses of several OpenStack Neutron plugins and supporting
|
||||
code.
|
||||
|
||||
* [Astara Horizon](https://github.com/openstack/astara-horizon) -
|
||||
OpenStack Horizon Dashboard code.
|
||||
|
||||
|
||||
## Project Details
|
||||
|
||||
Astara is publicly managed through the [Astara Launchpad project](https://launchpad.net/astara)
|
||||
|
||||
|
||||
## Code Review
|
||||
|
||||
The code goes to get reviewed by collaborators and merged at
|
||||
[OpenStack Gerrit review](https://review.openstack.org)
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
Can be found at [docs.akanda.io](http://docs.akanda.io)
|
||||
|
||||
Developer quick start guides for making this all work in Devstack `Here
|
||||
<docs/source/developer_quickstart.rst>`_
|
||||
|
||||
|
||||
## Community
|
||||
|
||||
Talk to the developers through IRC [#openstack-astara channel on freenode.net]
|
||||
(http://webchat.freenode.net/?randomnick=1&channels=%23openstack-astara&prompt=1&uio=d4)
|
||||
|
||||
|
||||
## License and Copyright
|
||||
|
||||
Astara is licensed under the Apache-2.0 license and is Copyright 2015,
|
||||
OpenStack Foundation
|
|
@ -0,0 +1,10 @@
|
|||
This project is no longer maintained.
|
||||
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
|
@ -1,33 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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 oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
api_opts = [
|
||||
cfg.StrOpt('admin_user'),
|
||||
cfg.StrOpt('admin_password', secret=True),
|
||||
cfg.StrOpt('admin_tenant_name'),
|
||||
cfg.StrOpt('auth_url'),
|
||||
cfg.StrOpt('auth_strategy', default='keystone'),
|
||||
cfg.StrOpt('auth_region'),
|
||||
cfg.IntOpt('max_retries', default=3),
|
||||
cfg.IntOpt('retry_delay', default=1),
|
||||
cfg.StrOpt('endpoint_type', default='publicURL'),
|
||||
]
|
||||
CONF.register_opts(api_opts)
|
|
@ -1,91 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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 requests
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
ASTARA_MGT_SERVICE_PORT = 5000
|
||||
ASTARA_BASE_PATH = '/v1/'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
AK_CLIENT_OPTS = [
|
||||
cfg.IntOpt('alive_timeout', default=3),
|
||||
cfg.IntOpt('config_timeout', default=90),
|
||||
]
|
||||
CONF.register_opts(AK_CLIENT_OPTS)
|
||||
|
||||
|
||||
def _mgt_url(host, port, path):
|
||||
if ':' in host:
|
||||
host = '[%s]' % host
|
||||
return 'http://%s:%s%s' % (host, port, path)
|
||||
|
||||
|
||||
def _get_proxyless_session():
|
||||
s = requests.Session()
|
||||
# ignore any proxy setting because we should have a direct connection
|
||||
s.trust_env = False
|
||||
return s
|
||||
|
||||
|
||||
def is_alive(host, port, timeout=None):
|
||||
timeout = timeout or cfg.CONF.alive_timeout
|
||||
path = ASTARA_BASE_PATH + 'firewall/rules'
|
||||
try:
|
||||
s = _get_proxyless_session()
|
||||
r = s.get(_mgt_url(host, port, path), timeout=timeout)
|
||||
if r.status_code == 200:
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.debug('is_alive for %s failed: %s', host, str(e))
|
||||
return False
|
||||
|
||||
|
||||
def get_interfaces(host, port):
|
||||
path = ASTARA_BASE_PATH + 'system/interfaces'
|
||||
s = _get_proxyless_session()
|
||||
r = s.get(_mgt_url(host, port, path), timeout=30)
|
||||
return r.json().get('interfaces', [])
|
||||
|
||||
|
||||
def update_config(host, port, config_dict):
|
||||
path = ASTARA_BASE_PATH + 'system/config'
|
||||
headers = {'Content-type': 'application/json'}
|
||||
|
||||
s = _get_proxyless_session()
|
||||
r = s.put(
|
||||
_mgt_url(host, port, path),
|
||||
data=jsonutils.dump_as_bytes(config_dict),
|
||||
headers=headers,
|
||||
timeout=cfg.CONF.config_timeout)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise Exception('Config update failed: %s' % r.text)
|
||||
else:
|
||||
return r.json()
|
||||
|
||||
|
||||
def read_labels(host, port):
|
||||
path = ASTARA_BASE_PATH + 'firewall/labels'
|
||||
s = _get_proxyless_session()
|
||||
r = s.post(_mgt_url(host, port, path), timeout=30)
|
||||
return r.json().get('labels', [])
|
|
@ -1,105 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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 re
|
||||
|
||||
from astara.common import constants
|
||||
|
||||
SERVICE_STATIC = 'static'
|
||||
|
||||
|
||||
def network_config(client, port, ifname, network_type, network_ports=[]):
|
||||
network = client.get_network_detail(port.network_id)
|
||||
subnets_dict = dict((s.id, s) for s in network.subnets)
|
||||
|
||||
return _make_network_config_dict(
|
||||
_interface_config(ifname, port, subnets_dict, network.mtu),
|
||||
network_type,
|
||||
port.network_id,
|
||||
mtu=network.mtu,
|
||||
subnets_dict=subnets_dict,
|
||||
network_ports=network_ports)
|
||||
|
||||
|
||||
def _make_network_config_dict(interface, network_type, network_id, mtu=None,
|
||||
v4_conf=SERVICE_STATIC, v6_conf=SERVICE_STATIC,
|
||||
subnets_dict={}, network_ports=[]):
|
||||
return {'interface': interface,
|
||||
'network_id': network_id,
|
||||
'mtu': mtu,
|
||||
'v4_conf_service': v4_conf,
|
||||
'v6_conf_service': v6_conf,
|
||||
'network_type': network_type,
|
||||
'subnets': [_subnet_config(s) for s in subnets_dict.values()],
|
||||
'allocations': _allocation_config(network_ports, subnets_dict)}
|
||||
|
||||
|
||||
def _interface_config(ifname, port, subnets_dict, mtu):
|
||||
def fmt(fixed):
|
||||
return '%s/%s' % (fixed.ip_address,
|
||||
subnets_dict[fixed.subnet_id].cidr.prefixlen)
|
||||
|
||||
retval = {'ifname': ifname,
|
||||
'addresses': [fmt(fixed) for fixed in port.fixed_ips]}
|
||||
if mtu:
|
||||
retval['mtu'] = mtu
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
def _subnet_config(subnet):
|
||||
return {
|
||||
'id': str(subnet.id),
|
||||
'cidr': str(subnet.cidr),
|
||||
'dhcp_enabled': subnet.enable_dhcp and subnet.ipv6_ra_mode != 'slaac',
|
||||
'dns_nameservers': subnet.dns_nameservers,
|
||||
'host_routes': subnet.host_routes,
|
||||
'gateway_ip': (str(subnet.gateway_ip)
|
||||
if subnet.gateway_ip is not None
|
||||
else ''),
|
||||
}
|
||||
|
||||
|
||||
def _allocation_config(ports, subnets_dict):
|
||||
r = re.compile('[:.]')
|
||||
service_ports_re = re.compile(
|
||||
'^ASTARA:(' + '|'.join(constants.ASTARA_SERVICE_PORT_TYPES) + '):.*$'
|
||||
)
|
||||
allocations = []
|
||||
|
||||
for port in ports:
|
||||
if service_ports_re.match(port.name):
|
||||
continue
|
||||
|
||||
addrs = {
|
||||
str(fixed.ip_address): subnets_dict[fixed.subnet_id].enable_dhcp
|
||||
for fixed in port.fixed_ips
|
||||
}
|
||||
|
||||
if not addrs:
|
||||
continue
|
||||
|
||||
allocations.append(
|
||||
{
|
||||
'ip_addresses': addrs,
|
||||
'device_id': port.device_id,
|
||||
'hostname': '%s.local' % r.sub('-', sorted(addrs.keys())[0]),
|
||||
'mac_address': port.mac_address
|
||||
}
|
||||
)
|
||||
|
||||
return allocations
|
|
@ -1,49 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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 oslo_log import log as logging
|
||||
|
||||
from astara.api.config import common
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_config(client, loadbalancer, management_port, iface_map):
|
||||
LOG.debug('Generating configuration for loadbalancer %s', loadbalancer.id)
|
||||
|
||||
network_config = [
|
||||
common.network_config(
|
||||
client,
|
||||
loadbalancer.vip_port,
|
||||
iface_map[loadbalancer.vip_port.network_id],
|
||||
'loadbalancer'),
|
||||
|
||||
common.network_config(
|
||||
client,
|
||||
management_port,
|
||||
iface_map[management_port.network_id],
|
||||
'management'),
|
||||
]
|
||||
|
||||
out = {
|
||||
'hostname': 'ak-loadbalancer-%s' % loadbalancer.tenant_id,
|
||||
'tenant_id': loadbalancer.tenant_id,
|
||||
'networks': network_config,
|
||||
'services': {
|
||||
'loadbalancer': loadbalancer.to_dict()
|
||||
}
|
||||
}
|
||||
return out
|
|
@ -1,171 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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 netaddr
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from astara.common.i18n import _LI, _LW
|
||||
from astara.api.config import common
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_AS = 64512
|
||||
|
||||
OPTIONS = [
|
||||
cfg.StrOpt('provider_rules_path',
|
||||
default='/etc/astara/provider_rules.json'),
|
||||
cfg.IntOpt('asn', default=DEFAULT_AS),
|
||||
cfg.IntOpt('neighbor_asn', default=DEFAULT_AS),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(OPTIONS)
|
||||
|
||||
EXTERNAL_NET = 'external'
|
||||
INTERNAL_NET = 'internal'
|
||||
MANAGEMENT_NET = 'management'
|
||||
SERVICE_STATIC = 'static'
|
||||
SERVICE_DHCP = 'dhcp'
|
||||
SERVICE_RA = 'ra'
|
||||
|
||||
|
||||
def build_config(worker_context, router, management_port, interfaces):
|
||||
provider_rules = load_provider_rules(cfg.CONF.provider_rules_path)
|
||||
|
||||
networks = generate_network_config(
|
||||
worker_context.neutron,
|
||||
router,
|
||||
management_port,
|
||||
interfaces
|
||||
)
|
||||
gateway = get_default_v4_gateway(
|
||||
worker_context.neutron, router, networks)
|
||||
|
||||
return {
|
||||
'asn': cfg.CONF.asn,
|
||||
'neighbor_asn': cfg.CONF.neighbor_asn,
|
||||
'default_v4_gateway': gateway,
|
||||
'networks': networks,
|
||||
'labels': provider_rules.get('labels', {}),
|
||||
'floating_ips': generate_floating_config(router),
|
||||
'tenant_id': router.tenant_id,
|
||||
'hostname': 'ak-%s' % router.tenant_id,
|
||||
'orchestrator': worker_context.config,
|
||||
'ha_resource': router.ha,
|
||||
'vpn': generate_vpn_config(router, worker_context.neutron),
|
||||
}
|
||||
|
||||
|
||||
def get_default_v4_gateway(client, router, networks):
|
||||
"""Find the IPv4 default gateway for the router.
|
||||
"""
|
||||
LOG.debug('networks = %r', networks)
|
||||
if router.external_port:
|
||||
LOG.debug('external interface = %s', router.external_port.mac_address)
|
||||
|
||||
# Now find the subnet that our external IP is on, and return its
|
||||
# gateway.
|
||||
for n in networks:
|
||||
if n['network_type'] == EXTERNAL_NET:
|
||||
v4_addresses = [
|
||||
addr
|
||||
for addr in (netaddr.IPAddress(ip.partition('/')[0])
|
||||
for ip in n['interface']['addresses'])
|
||||
if addr.version == 4
|
||||
]
|
||||
for s in n['subnets']:
|
||||
subnet = netaddr.IPNetwork(s['cidr'])
|
||||
if subnet.version != 4:
|
||||
continue
|
||||
LOG.debug(
|
||||
'%s: checking if subnet %s should have the default route',
|
||||
router.id, s['cidr'])
|
||||
for addr in v4_addresses:
|
||||
if addr in subnet:
|
||||
LOG.debug(
|
||||
'%s: found gateway %s for subnet %s on network %s',
|
||||
router.id,
|
||||
s['gateway_ip'],
|
||||
s['cidr'],
|
||||
n['network_id'],
|
||||
)
|
||||
return s['gateway_ip']
|
||||
|
||||
# Sometimes we are asked to build a configuration for the server
|
||||
# when the external interface is still marked as "down". We can
|
||||
# report that case, but we don't treat it as an error here because
|
||||
# we'll be asked to do it again when the interface comes up.
|
||||
LOG.info(_LI('%s: no default gateway was found'), router.id)
|
||||
return ''
|
||||
|
||||
|
||||
def load_provider_rules(path):
|
||||
try:
|
||||
return jsonutils.load(open(path))
|
||||
except: # pragma nocover
|
||||
LOG.warning(_LW('unable to open provider rules: %s'), path)
|
||||
return {}
|
||||
|
||||
|
||||
def generate_network_config(client, router, management_port, iface_map):
|
||||
retval = [
|
||||
common.network_config(
|
||||
client,
|
||||
management_port,
|
||||
iface_map[management_port.network_id],
|
||||
MANAGEMENT_NET
|
||||
)
|
||||
]
|
||||
|
||||
if router.external_port:
|
||||
retval.extend([
|
||||
common.network_config(
|
||||
client,
|
||||
router.external_port,
|
||||
iface_map[router.external_port.network_id],
|
||||
EXTERNAL_NET)])
|
||||
|
||||
retval.extend(
|
||||
common.network_config(
|
||||
client,
|
||||
p,
|
||||
iface_map[p.network_id],
|
||||
INTERNAL_NET,
|
||||
client.get_network_ports(p.network_id))
|
||||
for p in router.internal_ports)
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
def generate_floating_config(router):
|
||||
return [
|
||||
{'floating_ip': str(fip.floating_ip), 'fixed_ip': str(fip.fixed_ip)}
|
||||
for fip in router.floating_ips
|
||||
]
|
||||
|
||||
|
||||
def generate_vpn_config(router, client):
|
||||
if not cfg.CONF.router.ipsec_vpn:
|
||||
return {}
|
||||
|
||||
return {
|
||||
'ipsec': [
|
||||
v.to_dict() for v in client.get_vpnservices_for_router(router.id)
|
||||
]
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2015 Akanda, Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient import auth as ksauth
|
||||
from keystoneclient import session as kssession
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class KeystoneSession(object):
|
||||
def __init__(self):
|
||||
self._session = None
|
||||
self.region_name = CONF.auth_region
|
||||
ksauth.register_conf_options(CONF, 'keystone_authtoken')
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
if not self._session:
|
||||
# Construct a Keystone session for configured auth_plugin
|
||||
# and credentials
|
||||
auth_plugin = ksauth.load_from_conf_options(
|
||||
cfg.CONF, 'keystone_authtoken')
|
||||
self._session = kssession.Session(auth=auth_plugin)
|
||||
return self._session
|
File diff suppressed because it is too large
Load Diff
|
@ -1,453 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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 datetime import datetime
|
||||
import time
|
||||
|
||||
import netaddr
|
||||
from novaclient import client
|
||||
from novaclient import exceptions as novaclient_exceptions
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from astara.common.i18n import _LW, _LE, _LI
|
||||
from astara.api import keystone
|
||||
from astara.api import neutron
|
||||
from astara.common import config
|
||||
from astara.pez import rpcapi as pez_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
OPTIONS = [
|
||||
cfg.StrOpt(
|
||||
'ssh_public_key',
|
||||
help="Path to the SSH public key for the 'astara' user within "
|
||||
"appliance instances",
|
||||
default='/etc/astara/astara.pub'),
|
||||
cfg.StrOpt(
|
||||
'instance_provider', default='on_demand',
|
||||
help='Which instance provider to use (on_demand, pez)'),
|
||||
cfg.StrOpt(
|
||||
'astara_boot_command', default='astara-configure-management',
|
||||
help='The boot command to run to configure the appliance'),
|
||||
]
|
||||
cfg.CONF.register_opts(OPTIONS)
|
||||
|
||||
|
||||
class NovaInstanceDeleteTimeout(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InstanceInfo(object):
|
||||
def __init__(self, instance_id, name, management_port=None, ports=(),
|
||||
image_uuid=None, status=None, last_boot=None):
|
||||
self.id_ = instance_id
|
||||
self.name = name
|
||||
self.image_uuid = image_uuid
|
||||
|
||||
self.nova_status = status
|
||||
|
||||
self.management_port = management_port
|
||||
self._ports = ports
|
||||
self.last_boot = last_boot
|
||||
|
||||
@property
|
||||
def booting(self):
|
||||
return 'BUILD' in self.nova_status
|
||||
|
||||
@property
|
||||
def management_address(self):
|
||||
if self.management_port:
|
||||
return str(self.management_port.fixed_ips[0].ip_address)
|
||||
|
||||
@property
|
||||
def time_since_boot(self):
|
||||
if self.last_boot:
|
||||
return datetime.utcnow() - self.last_boot
|
||||
|
||||
@property
|
||||
def ports(self):
|
||||
return self._ports
|
||||
|
||||
@ports.setter
|
||||
def ports(self, port_list):
|
||||
self._ports = [p for p in port_list if p != self.management_port]
|
||||
|
||||
@classmethod
|
||||
def from_nova(cls, instance):
|
||||
"""
|
||||
Returns an instantiated InstanceInfo object with data gathered from
|
||||
an existing Nova server.
|
||||
|
||||
:param instance: novaclient.v2.servers.Server object for an existing
|
||||
nova instance.
|
||||
:returns: InstanceInfo instance
|
||||
"""
|
||||
# NOTE(adam_g): We do not yet actually rebuild any instances.
|
||||
# A rug REBUILD is actually a delete/create, so it
|
||||
# should be safe to track last_boot as the timestamp
|
||||
# the instance was last booted.
|
||||
last_boot = datetime.strptime(
|
||||
instance.created, "%Y-%m-%dT%H:%M:%SZ")
|
||||
return cls(
|
||||
instance_id=instance.id,
|
||||
name=instance.name,
|
||||
image_uuid=instance.image['id'],
|
||||
status=instance.status,
|
||||
last_boot=last_boot,
|
||||
)
|
||||
|
||||
|
||||
class InstanceProvider(object):
|
||||
def __init__(self, client):
|
||||
self.nova_client = client
|
||||
LOG.debug(_LI(
|
||||
'Initialized %s with novaclient %s'),
|
||||
self.__class__.__name__, self.nova_client)
|
||||
|
||||
def create_instance(self, driver, name, image_uuid, flavor,
|
||||
make_ports_callback):
|
||||
"""Create or get an instance
|
||||
|
||||
:param router_id: UUID of the resource that the instance will host
|
||||
|
||||
:returns: InstanceInfo object with at least id, name and image_uuid
|
||||
set.
|
||||
"""
|
||||
|
||||
|
||||
class PezInstanceProvider(InstanceProvider):
|
||||
def __init__(self, client):
|
||||
super(PezInstanceProvider, self).__init__(client)
|
||||
self.rpc_client = pez_api.AstaraPezAPI(rpc_topic='astara-pez')
|
||||
LOG.debug(_LI(
|
||||
'Initialized %s with rpc client %s'),
|
||||
self.__class__.__name__, self.rpc_client)
|
||||
|
||||
def create_instance(self, resource_type, name, image_uuid, flavor,
|
||||
make_ports_callback):
|
||||
# TODO(adam_g): pez already creates the mgt port on boot and the one
|
||||
# we create here is wasted. callback needs to be adjusted
|
||||
mgt_port, instance_ports = make_ports_callback()
|
||||
|
||||
mgt_port_dict = {
|
||||
'id': mgt_port.id,
|
||||
'network_id': mgt_port.network_id,
|
||||
}
|
||||
instance_ports_dicts = [{
|
||||
'id': p.id, 'network_id': p.network_id,
|
||||
} for p in instance_ports]
|
||||
|
||||
LOG.debug('Requesting new %s instance from Pez.', resource_type)
|
||||
pez_instance = self.rpc_client.get_instance(
|
||||
resource_type, name, mgt_port_dict, instance_ports_dicts)
|
||||
LOG.debug('Got %s instance %s from Pez.',
|
||||
resource_type, pez_instance['id'])
|
||||
|
||||
server = self.nova_client.servers.get(pez_instance['id'])
|
||||
|
||||
# deserialize port data
|
||||
mgt_port = neutron.Port.from_dict(pez_instance['management_port'])
|
||||
instance_ports = [
|
||||
neutron.Port.from_dict(p)
|
||||
for p in pez_instance['instance_ports']]
|
||||
|
||||
boot_time = datetime.strptime(
|
||||
server.created, "%Y-%m-%dT%H:%M:%SZ")
|
||||
instance_info = InstanceInfo(
|
||||
instance_id=server.id,
|
||||
name=server.name,
|
||||
management_port=mgt_port,
|
||||
ports=instance_ports,
|
||||
image_uuid=image_uuid,
|
||||
status=server.status,
|
||||
last_boot=boot_time)
|
||||
|
||||
return instance_info
|
||||
|
||||
|
||||
class OnDemandInstanceProvider(InstanceProvider):
|
||||
def create_instance(self, resource_type, name, image_uuid, flavor,
|
||||
make_ports_callback):
|
||||
mgt_port, instance_ports = make_ports_callback()
|
||||
|
||||
nics = [{'net-id': p.network_id,
|
||||
'v4-fixed-ip': '',
|
||||
'port-id': p.id}
|
||||
for p in ([mgt_port] + instance_ports)]
|
||||
|
||||
LOG.debug('creating instance %s with image %s',
|
||||
name, image_uuid)
|
||||
|
||||
server = self.nova_client.servers.create(
|
||||
name,
|
||||
image=image_uuid,
|
||||
flavor=flavor,
|
||||
nics=nics,
|
||||
config_drive=True,
|
||||
userdata=format_userdata(mgt_port)
|
||||
)
|
||||
|
||||
server_status = None
|
||||
for i in range(1, 10):
|
||||
try:
|
||||
# novaclient loads attributes lazily and we need to wait until
|
||||
# the client object is populated. moving to keystone sessions
|
||||
# exposes this race.
|
||||
server_status = server.status
|
||||
except AttributeError:
|
||||
time.sleep(.5)
|
||||
assert server_status
|
||||
|
||||
boot_time = datetime.strptime(
|
||||
server.created, "%Y-%m-%dT%H:%M:%SZ")
|
||||
instance_info = InstanceInfo(
|
||||
instance_id=server.id,
|
||||
name=name,
|
||||
management_port=mgt_port,
|
||||
ports=instance_ports,
|
||||
image_uuid=image_uuid,
|
||||
status=server.status,
|
||||
last_boot=boot_time)
|
||||
|
||||
return instance_info
|
||||
|
||||
INSTANCE_PROVIDERS = {
|
||||
'on_demand': OnDemandInstanceProvider,
|
||||
'pez': PezInstanceProvider,
|
||||
'default': OnDemandInstanceProvider,
|
||||
}
|
||||
|
||||
|
||||
def get_instance_provider(provider):
|
||||
try:
|
||||
return INSTANCE_PROVIDERS[provider]
|
||||
except KeyError:
|
||||
default = INSTANCE_PROVIDERS['default']
|
||||
LOG.error(_LE('Could not find %s instance provider, using default %s'),
|
||||
provider, default)
|
||||
return default
|
||||
|
||||
|
||||
class Nova(object):
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
ks_session = keystone.KeystoneSession()
|
||||
self.client = client.Client(
|
||||
version='2',
|
||||
session=ks_session.session,
|
||||
region_name=conf.auth_region,
|
||||
endpoint_type=conf.endpoint_type)
|
||||
|
||||
try:
|
||||
self.instance_provider = get_instance_provider(
|
||||
conf.instance_provider)(self.client)
|
||||
except AttributeError:
|
||||
default = INSTANCE_PROVIDERS['default']
|
||||
LOG.error(_LE('Could not find provider config, using default %s'),
|
||||
default)
|
||||
self.instance_provider = default(self.client)
|
||||
|
||||
def get_instances_for_obj(self, name):
|
||||
"""Retrieves all nova servers for a given instance name.
|
||||
|
||||
:param name: name of the instance being queried
|
||||
|
||||
:returns: a list of novaclient.v2.servers.Server objects or []
|
||||
"""
|
||||
search_opt = '^' + name + '.*$'
|
||||
instances = self.client.servers.list(
|
||||
search_opts=dict(name=search_opt)
|
||||
)
|
||||
if not instances:
|
||||
return []
|
||||
return [InstanceInfo.from_nova(i) for i in instances]
|
||||
|
||||
def get_instance_for_obj(self, name):
|
||||
"""Retrieves a nova server for a given instance name.
|
||||
|
||||
:param name: name of the instance being queried
|
||||
|
||||
:returns: a novaclient.v2.servers.Server object or None
|
||||
"""
|
||||
instances = self.client.servers.list(
|
||||
search_opts=dict(name=name)
|
||||
)
|
||||
|
||||
if instances:
|
||||
return instances[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_instance_by_id(self, instance_id):
|
||||
"""Retrieves a nova server for a given instance_id.
|
||||
|
||||
:param instance_id: Nova instance ID of instance being queried
|
||||
|
||||
:returns: a novaclient.v2.servers.Server object
|
||||
"""
|
||||
try:
|
||||
return self.client.servers.get(instance_id)
|
||||
except novaclient_exceptions.NotFound:
|
||||
return None
|
||||
|
||||
def destroy_instance(self, instance_info):
|
||||
if instance_info:
|
||||
LOG.debug('deleting instance %s', instance_info.name)
|
||||
self.client.servers.delete(instance_info.id_)
|
||||
|
||||
def boot_instance(self,
|
||||
resource_type,
|
||||
prev_instance_info,
|
||||
name,
|
||||
image_uuid,
|
||||
flavor,
|
||||
make_ports_callback):
|
||||
|
||||
if not prev_instance_info:
|
||||
instance = self.get_instance_for_obj(name)
|
||||
else:
|
||||
instance = self.get_instance_by_id(prev_instance_info.id_)
|
||||
|
||||
# check to make sure this instance isn't pre-existing
|
||||
if instance:
|
||||
if 'BUILD' in instance.status:
|
||||
if prev_instance_info:
|
||||
# if we had previous instance, return the same instance
|
||||
# with updated status
|
||||
prev_instance_info.nova_status = instance.status
|
||||
instance_info = prev_instance_info
|
||||
else:
|
||||
instance_info = InstanceInfo.from_nova(instance)
|
||||
return instance_info
|
||||
|
||||
self.client.servers.delete(instance.id)
|
||||
return None
|
||||
|
||||
# it is now safe to attempt boot
|
||||
instance_info = self.instance_provider.create_instance(
|
||||
resource_type=resource_type,
|
||||
name=name,
|
||||
image_uuid=image_uuid,
|
||||
flavor=flavor,
|
||||
make_ports_callback=make_ports_callback
|
||||
)
|
||||
return instance_info
|
||||
|
||||
def update_instance_info(self, instance_info):
|
||||
"""Used primarily for updating tracked instance status"""
|
||||
instance = self.get_instance_by_id(instance_info.id_)
|
||||
if not instance:
|
||||
return None
|
||||
instance_info.nova_status = instance.status
|
||||
return instance_info
|
||||
|
||||
def delete_instances_and_wait(self, instance_infos):
|
||||
"""Deletes the nova instance and waits for its deletion to complete"""
|
||||
to_poll = list(instance_infos)
|
||||
|
||||
for inst in instance_infos:
|
||||
try:
|
||||
self.destroy_instance(inst)
|
||||
except novaclient_exceptions.NotFound:
|
||||
pass
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
_LE('Error deleting instance %s' % inst.id_))
|
||||
to_poll.remove(inst)
|
||||
|
||||
# XXX parallelize this
|
||||
timed_out = []
|
||||
for inst in to_poll:
|
||||
start = time.time()
|
||||
i = 0
|
||||
while time.time() - start < cfg.CONF.boot_timeout:
|
||||
i += 1
|
||||
if not self.get_instance_by_id(inst.id_):
|
||||
LOG.debug('Instance %s has been deleted', inst.id_)
|
||||
break
|
||||
LOG.debug(
|
||||
'Instance %s has not finished stopping', inst.id_)
|
||||
time.sleep(cfg.CONF.retry_delay)
|
||||
else:
|
||||
timed_out.append(inst)
|
||||
LOG.error(_LE(
|
||||
'Instance %s failed to stop within %d secs'),
|
||||
inst.id_, cfg.CONF.boot_timeout)
|
||||
|
||||
if timed_out:
|
||||
raise NovaInstanceDeleteTimeout()
|
||||
|
||||
|
||||
# TODO(mark): Convert this to dynamic yaml, proper network prefix and ssh-keys
|
||||
|
||||
TEMPLATE = """#cloud-config
|
||||
|
||||
cloud_config_modules:
|
||||
- emit_upstart
|
||||
- set_hostname
|
||||
- locale
|
||||
- set-passwords
|
||||
- timezone
|
||||
- disable-ec2-metadata
|
||||
- runcmd
|
||||
|
||||
output: {all: '| tee -a /var/log/cloud-init-output.log'}
|
||||
|
||||
debug:
|
||||
- verbose: true
|
||||
|
||||
bootcmd:
|
||||
- /usr/local/bin/%(boot_command)s %(mac_address)s %(ip_address)s/%(prefix)d
|
||||
|
||||
users:
|
||||
- name: astara
|
||||
gecos: Astara
|
||||
groups: users
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
lock-passwd: true
|
||||
ssh-authorized-keys:
|
||||
- %(ssh_public_key)s
|
||||
|
||||
final_message: "Astara appliance is running"
|
||||
""" # noqa
|
||||
|
||||
|
||||
def _ssh_key():
|
||||
key = config.get_best_config_path(cfg.CONF.ssh_public_key)
|
||||
if not key:
|
||||
return ''
|
||||
try:
|
||||
with open(key) as out:
|
||||
return out.read().strip()
|
||||
except IOError:
|
||||
LOG.warning(_LW('Could not load router ssh public key from %s'), key)
|
||||
return ''
|
||||
|
||||
|
||||
def format_userdata(mgt_port):
|
||||
mgt_net = netaddr.IPNetwork(cfg.CONF.management_prefix)
|
||||
ctxt = {
|
||||
'ssh_public_key': _ssh_key(),
|
||||
'mac_address': mgt_port.mac_address,
|
||||
'ip_address': mgt_port.fixed_ips[0].ip_address,
|
||||
'boot_command': cfg.CONF.astara_boot_command,
|
||||
'prefix': mgt_net.prefixlen
|
||||
}
|
||||
out = TEMPLATE % ctxt
|
||||
LOG.debug('Rendered cloud-init for instance: %s' % out)
|
||||
return out
|
|
@ -1,125 +0,0 @@
|
|||
# Copyright 2015 Akanda, Inc
|
||||
#
|
||||
# Author: Akanda, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import socket
|
||||
|
||||
import eventlet
|
||||
import eventlet.wsgi
|
||||
import webob
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
import six
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from astara.cli import app
|
||||
from astara.common.i18n import _, _LE, _LI, _LW
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
RUG_API_OPTS = [
|
||||
cfg.IntOpt('api_port', default=44250,
|
||||
help='Astara administrative API listening port',
|
||||
deprecated_opts=[
|
||||
cfg.DeprecatedOpt('rug_api_port',
|
||||
group='DEFAULT')]),
|
||||
cfg.StrOpt('api_listen', default='0.0.0.0',
|
||||
help='Astara administrative API listening address')
|
||||
]
|
||||
cfg.CONF.register_opts(RUG_API_OPTS)
|
||||
|
||||
|
||||
class RugAPI(object):
|
||||
|
||||
def __init__(self, ctl=app.RugController):
|
||||
self.ctl = ctl()
|
||||
|
||||
@webob.dec.wsgify(RequestClass=webob.Request)
|
||||
def __call__(self, req):
|
||||
try:
|
||||
if req.method != 'PUT':
|
||||
return webob.exc.HTTPMethodNotAllowed()
|
||||
|
||||
args = filter(None, req.path.split('/'))
|
||||
if not args:
|
||||
return webob.exc.HTTPNotFound()
|
||||
|
||||
command, _, _ = self.ctl.command_manager.find_command(args)
|
||||
if command.interactive:
|
||||
return webob.exc.HTTPNotImplemented()
|
||||
|
||||
return str(self.ctl.run(['--debug'] + args))
|
||||
except SystemExit:
|
||||
# cliff invokes -h (help) on argparse failure
|
||||
# (which in turn results in sys.exit call)
|
||||
return webob.exc.HTTPBadRequest()
|
||||
except ValueError:
|
||||
return webob.exc.HTTPNotFound()
|
||||
except Exception:
|
||||
LOG.exception(_LE("Unexpected error."))
|
||||
msg = _('An unknown error has occurred. '
|
||||
'Please try your request again.')
|
||||
return webob.exc.HTTPInternalServerError(
|
||||
explanation=six.text_type(msg))
|
||||
|
||||
|
||||
class RugAPIServer(object):
|
||||
def __init__(self):
|
||||
self.pool = eventlet.GreenPool(1000)
|
||||
|
||||
def run(self, ip_address, port):
|
||||
app = RugAPI()
|
||||
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, ip_address)
|
||||
family = socket.AF_INET6
|
||||
except Exception:
|
||||
family = socket.AF_INET
|
||||
|
||||
for i in six.moves.range(5):
|
||||
LOG.info(_LI(
|
||||
'Starting the rug-api on %s:%s'),
|
||||
ip_address, port,
|
||||
)
|
||||
try:
|
||||
sock = eventlet.listen(
|
||||
(ip_address, port),
|
||||
family=family,
|
||||
backlog=128
|
||||
)
|
||||
except socket.error as err:
|
||||
if err.errno != 99: # EADDRNOTAVAIL
|
||||
raise
|
||||
LOG.warning(_LW('Could not create rug-api socket: %s'), err)
|
||||
LOG.warning(_LW('Sleeping %s before trying again'), i + 1)
|
||||
eventlet.sleep(i + 1)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
raise RuntimeError(_(
|
||||
'Could not establish rug-api socket on %s:%s') %
|
||||
(ip_address, port)
|
||||
)
|
||||
eventlet.wsgi.server(
|
||||
sock,
|
||||
app,
|
||||
custom_pool=self.pool,
|
||||
log=LOG)
|
||||
|
||||
|
||||
def serve():
|
||||
RugAPIServer().run(cfg.CONF.api_listen, cfg.CONF.api_port)
|
|
@ -1,15 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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.
|
|
@ -1,49 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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
|
||||
|
||||
from cliff import app
|
||||
from cliff import commandmanager
|
||||
from oslo_config import cfg
|
||||
import pkg_resources
|
||||
|
||||
from astara.common import config
|
||||
|
||||
|
||||
class RugController(app.App):
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self):
|
||||
dist = pkg_resources.get_distribution('astara')
|
||||
super(RugController, self).__init__(
|
||||
description='controller for the Astara Orchestrator service',
|
||||
version=dist.version,
|
||||
command_manager=commandmanager.CommandManager('astara.cli'),
|
||||
)
|
||||
|
||||
def initialize_app(self, argv):
|
||||
# Quiet logging for some request library
|
||||
logging.getLogger('requests').setLevel(logging.WARN)
|
||||
|
||||
# Don't pass argv here because cfg.CONF will intercept the
|
||||
# help options and exit.
|
||||
cfg.CONF(['--config-file', config.get_best_config_path()],
|
||||
project='astara-orchestrator')
|
||||
self.rug_ini = cfg.CONF
|
||||
return super(RugController, self).initialize_app(argv)
|
|
@ -1,377 +0,0 @@
|
|||
# Copyright 2014 DreamHost, LLC
|
||||
#
|
||||
# Author: DreamHost, 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.
|
||||
|
||||
|
||||
"""Interactive CLI for rebuilding routers
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import logging
|
||||
import os
|
||||
import Queue
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import threading
|
||||
import six
|
||||
from contextlib import closing
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from blessed import Terminal
|
||||
except ImportError:
|
||||
# blessed is not part of openstack global-requirements.
|
||||
raise Exception("The 'blessed' python module is required to browse"
|
||||
" Astara routers. Please install and try again.")
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from astara import commands
|
||||
from astara.api import nova as nova_api
|
||||
from astara.api import neutron as neutron_api
|
||||
from astara.cli import message
|
||||
|
||||
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
||||
|
||||
cfg.CONF.import_opt('host', 'astara.main')
|
||||
|
||||
|
||||
class FakeConfig(object):
|
||||
|
||||
def __init__(self, admin_user, admin_password, tenant_name, auth_url,
|
||||
auth_strategy, auth_region, instance_provider):
|
||||
self.admin_user = admin_user
|
||||
self.admin_password = admin_password
|
||||
self |