coe: Add support for clusters
This allows us to start migrating the coe cloud layer to this newly introduced proxy layer. Change-Id: Ia9c622dc284234bb618c2caf3d035097bba58ec5 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
b1f15919e1
commit
88d8d53d46
@ -6,6 +6,7 @@ from openstack.baremetal_introspection import baremetal_introspection_service
|
||||
from openstack.block_storage import block_storage_service
|
||||
from openstack.clustering import clustering_service
|
||||
from openstack.compute import compute_service
|
||||
from openstack.container_infrastructure_management import container_infrastructure_management_service
|
||||
from openstack.database import database_service
|
||||
from openstack.dns import dns_service
|
||||
from openstack.identity import identity_service
|
||||
@ -55,9 +56,9 @@ class ServicesMixin:
|
||||
|
||||
application_catalog = service_description.ServiceDescription(service_type='application-catalog')
|
||||
|
||||
container_infrastructure_management = service_description.ServiceDescription(service_type='container-infrastructure-management')
|
||||
container_infrastructure = container_infrastructure_management
|
||||
container_infrastructure_management = container_infrastructure_management_service.ContainerInfrastructureManagementService(service_type='container-infrastructure-management')
|
||||
container_infra = container_infrastructure_management
|
||||
container_infrastructure = container_infrastructure_management
|
||||
|
||||
search = service_description.ServiceDescription(service_type='search')
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
# 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 openstack.container_infrastructure_management.v1 import _proxy
|
||||
from openstack import service_description
|
||||
|
||||
|
||||
class ContainerInfrastructureManagementService(
|
||||
service_description.ServiceDescription,
|
||||
):
|
||||
"""The container infrastructure management service."""
|
||||
|
||||
supported_versions = {
|
||||
'1': _proxy.Proxy,
|
||||
}
|
109
openstack/container_infrastructure_management/v1/_proxy.py
Normal file
109
openstack/container_infrastructure_management/v1/_proxy.py
Normal file
@ -0,0 +1,109 @@
|
||||
# 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 openstack.container_infrastructure_management.v1 import (
|
||||
cluster as _cluster
|
||||
)
|
||||
from openstack import proxy
|
||||
|
||||
|
||||
class Proxy(proxy.Proxy):
|
||||
|
||||
_resource_registry = {
|
||||
"cluster": _cluster.Cluster,
|
||||
}
|
||||
|
||||
def create_cluster(self, **attrs):
|
||||
"""Create a new cluster from attributes
|
||||
|
||||
:param dict attrs: Keyword arguments which will be used to create a
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`,
|
||||
comprised of the properties on the Cluster class.
|
||||
:returns: The results of cluster creation
|
||||
:rtype:
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
"""
|
||||
return self._create(_cluster.Cluster, **attrs)
|
||||
|
||||
def delete_cluster(self, cluster, ignore_missing=True):
|
||||
"""Delete a cluster
|
||||
|
||||
:param cluster: The value can be either the ID of a cluster or a
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
instance.
|
||||
:param bool ignore_missing: When set to ``False``
|
||||
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
|
||||
the cluster does not exist. When set to ``True``, no exception will
|
||||
be set when attempting to delete a nonexistent cluster.
|
||||
:returns: ``None``
|
||||
"""
|
||||
self._delete(_cluster.Cluster, cluster, ignore_missing=ignore_missing)
|
||||
|
||||
def find_cluster(self, name_or_id, ignore_missing=True):
|
||||
"""Find a single cluster
|
||||
|
||||
:param name_or_id: The name or ID of a cluster.
|
||||
:param bool ignore_missing: When set to ``False``
|
||||
:class:`~openstack.exceptions.ResourceNotFound` will be
|
||||
raised when the resource does not exist.
|
||||
When set to ``True``, None will be returned when
|
||||
attempting to find a nonexistent resource.
|
||||
:returns: One
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
or None
|
||||
"""
|
||||
return self._find(
|
||||
_cluster.Cluster,
|
||||
name_or_id,
|
||||
ignore_missing=ignore_missing,
|
||||
)
|
||||
|
||||
def get_cluster(self, cluster):
|
||||
"""Get a single cluster
|
||||
|
||||
:param cluster: The value can be the ID of a cluster or a
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
instance.
|
||||
|
||||
:returns: One
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
:raises: :class:`~openstack.exceptions.ResourceNotFound`
|
||||
when no resource can be found.
|
||||
"""
|
||||
return self._get(_cluster.Cluster, cluster)
|
||||
|
||||
def clusters(self, **query):
|
||||
"""Return a generator of clusters
|
||||
|
||||
:param kwargs query: Optional query parameters to be sent to limit
|
||||
the resources being returned.
|
||||
|
||||
:returns: A generator of cluster objects
|
||||
:rtype:
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
"""
|
||||
return self._list(_cluster.Cluster, **query)
|
||||
|
||||
def update_cluster(self, cluster, **attrs):
|
||||
"""Update a cluster
|
||||
|
||||
:param cluster: Either the id of a cluster or a
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
instance.
|
||||
:param attrs: The attributes to update on the cluster represented
|
||||
by ``cluster``.
|
||||
|
||||
:returns: The updated cluster
|
||||
:rtype:
|
||||
:class:`~openstack.container_infrastructure_management.v1.cluster.Cluster`
|
||||
"""
|
||||
return self._update(_cluster.Cluster, cluster, **attrs)
|
167
openstack/container_infrastructure_management/v1/cluster.py
Normal file
167
openstack/container_infrastructure_management/v1/cluster.py
Normal file
@ -0,0 +1,167 @@
|
||||
# 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 openstack import exceptions
|
||||
from openstack import resource
|
||||
from openstack import utils
|
||||
|
||||
|
||||
class Cluster(resource.Resource):
|
||||
|
||||
resources_key = 'clusters'
|
||||
base_path = '/clusters'
|
||||
|
||||
# capabilities
|
||||
allow_create = True
|
||||
allow_fetch = True
|
||||
allow_commit = True
|
||||
allow_delete = True
|
||||
allow_list = True
|
||||
allow_patch = True
|
||||
|
||||
#: The endpoint URL of COE API exposed to end-users.
|
||||
api_address = resource.Body('api_address')
|
||||
#: The UUID of the cluster template.
|
||||
cluster_template_id = resource.Body('cluster_template_id')
|
||||
#: Version info of chosen COE in bay/cluster for helping client in picking
|
||||
#: the right version of client.
|
||||
coe_version = resource.Body('coe_version')
|
||||
#: The timeout for cluster creation in minutes. The value expected is a
|
||||
#: positive integer. If the timeout is reached during cluster creation
|
||||
#: process, the operation will be aborted and the cluster status will be
|
||||
#: set to CREATE_FAILED. Defaults to 60.
|
||||
create_timeout = resource.Body('create_timeout', type=int)
|
||||
#: The date and time when the resource was created. The date and time stamp
|
||||
#: format is ISO 8601::
|
||||
#:
|
||||
#: CCYY-MM-DDThh:mm:ss±hh:mm
|
||||
#:
|
||||
#: For example, `2015-08-27T09:49:58-05:00`. The ±hh:mm value, if included,
|
||||
#: is the time zone as an offset from UTC.
|
||||
created_at = resource.Body('created_at')
|
||||
#: The custom discovery url for node discovery. This is used by the COE to
|
||||
#: discover the servers that have been created to host the containers. The
|
||||
#: actual discovery mechanism varies with the COE. In some cases, the
|
||||
#: service fills in the server info in the discovery service. In other
|
||||
#: cases,if the discovery_url is not specified, the service will use the
|
||||
#: public discovery service at https://discovery.etcd.io. In this case, the
|
||||
#: service will generate a unique url here for each bay and store the info
|
||||
#: for the servers.
|
||||
discovery_url = resource.Body('discovery_url')
|
||||
#: The name or ID of the network to provide connectivity to the internal
|
||||
#: network for the bay/cluster.
|
||||
fixed_network = resource.Body('fixed_network')
|
||||
#: The fixed subnet to use when allocating network addresses for nodes in
|
||||
#: bay/cluster.
|
||||
fixed_subnet = resource.Body('fixed_subnet')
|
||||
#: The flavor name or ID to use when booting the node servers. Defaults to
|
||||
#: m1.small.
|
||||
flavor_id = resource.Body('flavor_id')
|
||||
#: Whether to enable using the floating IP of cloud provider. Some cloud
|
||||
#: providers use floating IPs while some use public IPs. When set to true,
|
||||
#: floating IPs will be used. If this value is not provided, the value of
|
||||
#: ``floating_ip_enabled`` provided in the template will be used.
|
||||
is_floating_ip_enabled = resource.Body('floating_ip_enabled', type=bool)
|
||||
#: Whether to enable the master load balancer. Since multiple masters may
|
||||
#: exist in a bay/cluster, a Neutron load balancer is created to provide
|
||||
#: the API endpoint for the bay/cluster and to direct requests to the
|
||||
#: masters. In some cases, such as when the LBaaS service is not available,
|
||||
#: this option can be set to false to create a bay/cluster without the load
|
||||
#: balancer. In this case, one of the masters will serve as the API
|
||||
#: endpoint. The default is true, i.e. to create the load balancer for the
|
||||
#: bay.
|
||||
is_master_lb_enabled = resource.Body('master_lb_enabled', type=bool)
|
||||
#: The name of the SSH keypair to configure in the bay/cluster servers for
|
||||
#: SSH access. Users will need the key to be able to ssh to the servers in
|
||||
#: the bay/cluster. The login name is specific to the bay/cluster driver.
|
||||
#: For example, with fedora-atomic image the default login name is fedora.
|
||||
keypair = resource.Body('keypair')
|
||||
#: Arbitrary labels in the form of key=value pairs. The accepted keys and
|
||||
#: valid values are defined in the bay/cluster drivers. They are used as a
|
||||
#: way to pass additional parameters that are specific to a bay/cluster
|
||||
#: driver.
|
||||
labels = resource.Body('labels', type=list)
|
||||
#: A list of floating IPs of all master nodes.
|
||||
master_addresses = resource.Body('master_addresses', type=list)
|
||||
#: The number of servers that will serve as master for the bay/cluster. Set
|
||||
#: to more than 1 master to enable High Availability. If the option
|
||||
#: master-lb-enabled is specified in the baymodel/cluster template, the
|
||||
#: master servers will be placed in a load balancer pool. Defaults to 1.
|
||||
master_count = resource.Body('master_count', type=int)
|
||||
#: The flavor of the master node for this baymodel/cluster template.
|
||||
master_flavor_id = resource.Body('master_flavor_id')
|
||||
#: Name of the resource.
|
||||
name = resource.Body('name')
|
||||
#: The number of servers that will serve as node in the bay/cluster.
|
||||
#: Defaults to 1.
|
||||
node_count = resource.Body('node_count', type=int)
|
||||
#: A list of floating IPs of all servers that serve as nodes.
|
||||
node_addresses = resource.Body('node_addresses', type=list)
|
||||
#: The reference UUID of orchestration stack from Heat orchestration
|
||||
#: service.
|
||||
stack_id = resource.Body('stack_id')
|
||||
#: The current state of the bay/cluster.
|
||||
status = resource.Body('status')
|
||||
#: The reason of bay/cluster current status.
|
||||
status_reason = resource.Body('reason')
|
||||
#: The date and time when the resource was updated. The date and time stamp
|
||||
#: format is ISO 8601::
|
||||
#:
|
||||
#: CCYY-MM-DDThh:mm:ss±hh:mm
|
||||
#:
|
||||
#: For example, `2015-08-27T09:49:58-05:00`. The ±hh:mm value, if included,
|
||||
#: is the time zone as an offset from UTC. If the updated_at date and time
|
||||
#: stamp is not set, its value is null.
|
||||
updated_at = resource.Body('updated_at')
|
||||
#: The UUID of the cluster.
|
||||
uuid = resource.Body('uuid', alternate_id=True)
|
||||
|
||||
def resize(self, session, *, node_count, nodes_to_remove=None):
|
||||
"""Resize the cluster.
|
||||
|
||||
:param node_count: The number of servers that will serve as node in the
|
||||
bay/cluster. The default is 1.
|
||||
:param nodes_to_remove: The server ID list will be removed if
|
||||
downsizing the cluster.
|
||||
:returns: The UUID of the resized cluster.
|
||||
:raises: :exc:`~openstack.exceptions.ResourceNotFound` if
|
||||
the resource was not found.
|
||||
"""
|
||||
url = utils.urljoin(Cluster.base_path, self.id, 'actions', 'resize')
|
||||
headers = {'Accept': ''}
|
||||
body = {
|
||||
'node_count': node_count,
|
||||
'nodes_to_remove': nodes_to_remove,
|
||||
}
|
||||
response = session.post(url, json=body, headers=headers)
|
||||
exceptions.raise_from_response(response)
|
||||
return response['uuid']
|
||||
|
||||
def upgrade(self, session, *, cluster_template, max_batch_size=None):
|
||||
"""Upgrade the cluster.
|
||||
|
||||
:param cluster_template: The UUID of the cluster template.
|
||||
:param max_batch_size: The max batch size each time when doing upgrade.
|
||||
The default is 1
|
||||
:returns: The UUID of the updated cluster.
|
||||
:raises: :exc:`~openstack.exceptions.ResourceNotFound` if
|
||||
the resource was not found.
|
||||
"""
|
||||
url = utils.urljoin(Cluster.base_path, self.id, 'actions', 'upgrade')
|
||||
headers = {'Accept': ''}
|
||||
body = {
|
||||
'cluster_template': cluster_template,
|
||||
'max_batch_size': max_batch_size,
|
||||
}
|
||||
response = session.post(url, json=body, headers=headers)
|
||||
exceptions.raise_from_response(response)
|
||||
return response['uuid']
|
@ -0,0 +1,56 @@
|
||||
# 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 openstack.container_infrastructure_management.v1 import cluster
|
||||
from openstack.tests.unit import base
|
||||
|
||||
EXAMPLE = {
|
||||
"cluster_template_id": "0562d357-8641-4759-8fed-8173f02c9633",
|
||||
"create_timeout": 60,
|
||||
"discovery_url": None,
|
||||
"flavor_id": None,
|
||||
"keypair": "my_keypair",
|
||||
"labels": [],
|
||||
"master_count": 2,
|
||||
"master_flavor_id": None,
|
||||
"name": "k8s",
|
||||
"node_count": 2,
|
||||
}
|
||||
|
||||
|
||||
class TestCluster(base.TestCase):
|
||||
def test_basic(self):
|
||||
sot = cluster.Cluster()
|
||||
self.assertIsNone(sot.resource_key)
|
||||
self.assertEqual('clusters', sot.resources_key)
|
||||
self.assertEqual('/clusters', sot.base_path)
|
||||
self.assertTrue(sot.allow_create)
|
||||
self.assertTrue(sot.allow_fetch)
|
||||
self.assertTrue(sot.allow_commit)
|
||||
self.assertTrue(sot.allow_delete)
|
||||
self.assertTrue(sot.allow_list)
|
||||
|
||||
def test_make_it(self):
|
||||
sot = cluster.Cluster(**EXAMPLE)
|
||||
self.assertEqual(
|
||||
EXAMPLE['cluster_template_id'],
|
||||
sot.cluster_template_id,
|
||||
)
|
||||
self.assertEqual(EXAMPLE['create_timeout'], sot.create_timeout)
|
||||
self.assertEqual(EXAMPLE['discovery_url'], sot.discovery_url)
|
||||
self.assertEqual(EXAMPLE['flavor_id'], sot.flavor_id)
|
||||
self.assertEqual(EXAMPLE['keypair'], sot.keypair)
|
||||
self.assertEqual(EXAMPLE['labels'], sot.labels)
|
||||
self.assertEqual(EXAMPLE['master_count'], sot.master_count)
|
||||
self.assertEqual(EXAMPLE['master_flavor_id'], sot.master_flavor_id)
|
||||
self.assertEqual(EXAMPLE['name'], sot.name)
|
||||
self.assertEqual(EXAMPLE['node_count'], sot.node_count)
|
@ -103,11 +103,17 @@ def _get_aliases(service_type, aliases=None):
|
||||
|
||||
|
||||
def _find_service_description_class(service_type):
|
||||
package_name = 'openstack.{service_type}'.format(
|
||||
service_type=service_type).replace('-', '_')
|
||||
package_name = f'openstack.{service_type}'.replace('-', '_')
|
||||
module_name = service_type.replace('-', '_') + '_service'
|
||||
class_name = ''.join(
|
||||
[part.capitalize() for part in module_name.split('_')])
|
||||
[part.capitalize() for part in module_name.split('_')]
|
||||
)
|
||||
|
||||
# We have some exceptions :(
|
||||
# This should have been called 'shared-filesystem'
|
||||
if service_type == 'shared-file-system':
|
||||
class_name = 'SharedFilesystemService'
|
||||
|
||||
try:
|
||||
import_name = '.'.join([package_name, module_name])
|
||||
service_description_module = importlib.import_module(import_name)
|
||||
@ -116,10 +122,11 @@ def _find_service_description_class(service_type):
|
||||
# as an opt-in for people trying to figure out why something
|
||||
# didn't work.
|
||||
warnings.warn(
|
||||
"Could not import {service_type} service description: {e}".format(
|
||||
service_type=service_type, e=str(e)),
|
||||
ImportWarning)
|
||||
f"Could not import {service_type} service description: {str(e)}",
|
||||
ImportWarning,
|
||||
)
|
||||
return service_description.ServiceDescription
|
||||
|
||||
# There are no cases in which we should have a module but not the class
|
||||
# inside it.
|
||||
service_description_class = getattr(service_description_module, class_name)
|
||||
|
Loading…
x
Reference in New Issue
Block a user