Retire astara repo

Retire repository, following
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project

Change-Id: If5f7d284bd107a93edd9272ac0ed8e6d20ba5c51
changes/56/610356/1
Andreas Jaeger 2018-10-14 12:50:35 +02:00
parent e7f8940fa0
commit 7759e2fd82
204 changed files with 10 additions and 27927 deletions

53
.gitignore vendored
View File

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

View File

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

@ -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.

View File

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

10
README.rst Normal file
View File

@ -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.

View File

View File

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

View File

@ -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', [])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

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

View File

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