Browse Source

Fix issues failing CI pipeline

- Run codebase through YAPF for formatting
- Add tox configuration for yapf and pep8
- Fix some non-YAPF pep8 failures
- Enhance verify_site for better MaaS-integration testing
- Create initial basic functional test

Change-Id: Ie5b5275d7795693a6551764362aee916b99b3e56
changes/89/569189/1
Scott Hussey 4 years ago
parent
commit
e892df58dc
  1. 3
      .style.yapf
  2. 4
      Dockerfile
  3. 2
      docs/configuration.rst
  4. 86
      docs/getting_started.rst
  5. 7
      drydock_provisioner/cli/action.py
  6. 33
      drydock_provisioner/cli/commands.py
  7. 43
      drydock_provisioner/cli/design/actions.py
  8. 32
      drydock_provisioner/cli/design/commands.py
  9. 86
      drydock_provisioner/cli/part/actions.py
  10. 69
      drydock_provisioner/cli/part/commands.py
  11. 42
      drydock_provisioner/cli/task/actions.py
  12. 65
      drydock_provisioner/cli/task/commands.py
  13. 136
      drydock_provisioner/config.py
  14. 34
      drydock_provisioner/control/api.py
  15. 43
      drydock_provisioner/control/base.py
  16. 22
      drydock_provisioner/control/bootdata.py
  17. 123
      drydock_provisioner/control/designs.py
  18. 30
      drydock_provisioner/control/middleware.py
  19. 194
      drydock_provisioner/control/tasks.py
  20. 29
      drydock_provisioner/drivers/__init__.py
  21. 39
      drydock_provisioner/drivers/node/__init__.py
  22. 62
      drydock_provisioner/drivers/node/maasdriver/api_client.py
  23. 1456
      drydock_provisioner/drivers/node/maasdriver/driver.py
  24. 46
      drydock_provisioner/drivers/node/maasdriver/models/base.py
  25. 57
      drydock_provisioner/drivers/node/maasdriver/models/boot_resource.py
  26. 10
      drydock_provisioner/drivers/node/maasdriver/models/fabric.py
  27. 167
      drydock_provisioner/drivers/node/maasdriver/models/interface.py
  28. 16
      drydock_provisioner/drivers/node/maasdriver/models/iprange.py
  29. 111
      drydock_provisioner/drivers/node/maasdriver/models/machine.py
  30. 163
      drydock_provisioner/drivers/node/maasdriver/models/rack_controller.py
  31. 5
      drydock_provisioner/drivers/node/maasdriver/models/sshkey.py
  32. 42
      drydock_provisioner/drivers/node/maasdriver/models/subnet.py
  33. 35
      drydock_provisioner/drivers/node/maasdriver/models/tag.py
  34. 18
      drydock_provisioner/drivers/node/maasdriver/models/vlan.py
  35. 21
      drydock_provisioner/drivers/oob/__init__.py
  36. 23
      drydock_provisioner/drivers/oob/manual_driver/driver.py
  37. 239
      drydock_provisioner/drivers/oob/pyghmi_driver/__init__.py
  38. 32
      drydock_provisioner/drydock.py
  39. 28
      drydock_provisioner/drydock_client/client.py
  40. 32
      drydock_provisioner/drydock_client/session.py
  41. 2
      drydock_provisioner/error.py
  42. 46
      drydock_provisioner/ingester/__init__.py
  43. 2
      drydock_provisioner/ingester/plugins/__init__.py
  44. 173
      drydock_provisioner/ingester/plugins/yaml.py
  45. 7
      drydock_provisioner/objects/__init__.py
  46. 11
      drydock_provisioner/objects/base.py
  47. 81
      drydock_provisioner/objects/fields.py
  48. 161
      drydock_provisioner/objects/hostprofile.py
  49. 71
      drydock_provisioner/objects/hwprofile.py
  50. 80
      drydock_provisioner/objects/network.py
  51. 25
      drydock_provisioner/objects/node.py
  52. 7
      drydock_provisioner/objects/promenade.py
  53. 109
      drydock_provisioner/objects/site.py
  54. 25
      drydock_provisioner/objects/task.py
  55. 576
      drydock_provisioner/orchestrator/__init__.py
  56. 149
      drydock_provisioner/policy.py
  57. 36
      drydock_provisioner/statemgmt/__init__.py
  58. 2
      requirements-direct.txt
  59. 2
      requirements-test.txt
  60. 66
      setup.py
  61. 354
      tests/functional/armada.yaml.sub
  62. 349
      tests/functional/drydock.yaml.sub
  63. 62
      tests/functional/multirack_prep.sh
  64. 82
      tests/functional/promenade.yaml.sub
  65. 16
      tests/functional/rbac-generous-permissions.yaml
  66. 9
      tests/functional/set-env.sh
  67. 128
      tests/functional/test_basic_integration.sh
  68. 12
      tests/integration/test_maasdriver_client.py
  69. 35
      tests/integration/test_maasdriver_network.py
  70. 53
      tests/integration/test_orch_node_networks.py
  71. 15
      tests/unit/test_api_tasks.py
  72. 12
      tests/unit/test_apienforcer.py
  73. 91
      tests/unit/test_auth_middleware.py
  74. 60
      tests/unit/test_design_inheritance.py
  75. 80
      tests/unit/test_drydock_client.py
  76. 36
      tests/unit/test_ingester.py
  77. 9
      tests/unit/test_ingester_yaml.py
  78. 80
      tests/unit/test_models.py
  79. 21
      tests/unit/test_orch_generic.py
  80. 20
      tests/unit/test_policy_engine.py
  81. 8
      tests/unit/test_statemgmt.py
  82. 108
      tests/yaml_samples/fullsite.yaml
  83. 110
      tests/yaml_samples/fullsite_profiles.yaml
  84. 62
      tests/yaml_samples/multidoc.yaml
  85. 49
      tests/yaml_samples/singledoc.yaml
  86. 21
      tox.ini

3
.style.yapf

@ -0,0 +1,3 @@
[style]
based_on_style = pep8
column_limit = 119

4
Dockerfile

@ -32,8 +32,8 @@ RUN apt -qq update && \
libssl-dev --no-install-recommends
# Copy direct dependency requirements only to build a dependency layer
COPY ./requirements-direct.txt /tmp/drydock/
RUN pip3 install -r /tmp/drydock/requirements-direct.txt
COPY ./requirements-lock.txt /tmp/drydock/
RUN pip3 install -r /tmp/drydock/requirements-lock.txt
COPY . /tmp/drydock

2
docs/configuration.rst

@ -28,8 +28,6 @@ The service account must then be included in the drydock.conf::
delay_auth_decision = true
auth_type = password
auth_section = keystone_authtoken_password
[keystone_authtoken_password]
auth_url = http://<keystone_ip>:5000
project_name = service
project_domain_name = ucp

86
docs/getting_started.rst

@ -2,78 +2,58 @@
Installing Drydock in a Dev Environment
=======================================
Drydock runs in Python 3.x only and is tested on Ubuntu 16.04 standard
images. It is recommended that your development environment be a Ubuntu
16.04 virtual machine.
Bootstrap Kubernetes
--------------------
MaaS
----
You can bootstrap your Helm-enabled Kubernetes cluster via the Openstack-Helm
AIO_ http://openstack-helm.readthedocs.io/en/latest/install/developer/all-in-one.html
process or using the UCP Promenade_ https://github.com/att-comdev/promenade tool.
Drydock requires a downstream node provisioning service and currently
the only driver implemented is for Canonical MaaS. So to begin with
install MaaS following their instructions_ https://docs.ubuntu.com/maas/2.2/en/installconfig-package-install.
The MaaS region and rack controllers can be installed in the same VM
as Drydock or a separate VM.
Deploy Drydock and Dependencies
-------------------------------
On the VM that MaaS is installed on, create an admin user:
Drydock is most easily deployed using Armada to deploy the Drydock
container into a Kubernetes cluster via Helm charts. The Drydock chart
is in aic-helm_ https://github.com/att-comdev/aic-helm. It depends on
the deployments of the MaaS_ https://github.com/openstack/openstack-helm-addons chart
and the Keystone_ https://github.com/openstack/openstack-helm chart.
::
$ sudo maas createadmin --username=admin --email=admin@example.com
You can now access the MaaS UI by pointing a browser at http://maas_vm_ip:5240/MAAS
and follow the configuration journey_ https://docs.ubuntu.com/maas/2.2/en/installconfig-webui-conf-journey
to finish getting MaaS ready for use.
Drydock Configuration
---------------------
Clone the git repo and customize your configuration file
A integrated deployment of these charts can be accomplished using the
Armada_ https://github.com/att-comdev/armada tool. An example integration
chart can be found in the UCP integrations_ https://github.com/att-comdev/ucp-integration
repo in the manifests/basic_ucp directory.
::
$ git clone https://github.com/att-comdev/ucp-integration
$ sudo docker run -ti -v $(pwd):/target -v ~/.kube:/armaada/.kube quay.io/attcomdev/armada:master apply --tiller-host <host_ip> --tiller-port 44134 /target/manifests/basic_ucp/ucp-armada.yaml
$ # wait for all pods in kubectl get pods -n ucp are 'Running'
$ KS_POD=$(kubectl get pods -n ucp | grep keystone | cut -d' ' -f1)
$ TOKEN=$(docker run --rm --net=host -e 'OS_AUTH_URL=http://keystone-api.ucp.svc.cluster.local:80/v3' -e 'OS_PASSWORD=password' -e 'OS_PROJECT_DOMAIN_NAME=default' -e 'OS_PROJECT_NAME=service' -e 'OS_REGION_NAME=RegionOne' -e 'OS_USERNAME=drydock' -e 'OS_USER_DOMAIN_NAME=default' -e 'OS_IDENTITY_API_VERSION=3' kolla/ubuntu-source-keystone:3.0.3 openstack token issue -f shell | grep ^id | cut -d'=' -f2 | tr -d '"')
$ docker run --rm -ti --net=host -e "DD_TOKEN=$TOKEN" -e "DD_URL=http://drydock-api.ucp.svc.cluster.local:9000" -e "LC_ALL=C.UTF-8" -e "LANG=C.UTF-8" $DRYDOCK_IMAGE /bin/bash
git clone https://github.com/att-comdev/drydock
cd drydock
tox -e genconfig
cp -r etc /tmp/drydock-etc
In `/tmp/drydock-etc/drydock/drydock.conf` customize your maas_api_url to be
the URL you used when opening the web UI and maas_api_key.
When starting the Drydock container, /tmp/drydock-etc/drydock will be
mounted as /etc/drydock with your customized configuration.
Drydock
-------
Drydock is easily installed via the Docker image at quay.io/attcomdev/drydock:latest.
You will need to customize and mount your configuration file
::
$ sudo docker run -v /tmp/drydock-etc/drydock:/etc/drydock -P -d drydock:latest
Configure Site
--------------
Load Site
---------
To use Drydock for site configuration, you must craft and load a site topology
YAML. An example of this is in examples/designparts_v1.0.yaml.
Load Site
---------
Documentation on building your topology document is under construction
Use the Drydock CLI create a design and load the configuration
::
$ drydock --token <token> --url <drydock_url> design create
$ drydock --token <token> --url <drydock_url> part create -d <design_id> -f <yaml_file>
# drydock design create
# drydock part create -d <design_id> -f <yaml_file>
Use the CLI to create tasks to deploy your site
::
$ drydock --token <token> --url <drydock_url> task create -d <design_id> -a verify_site
$ drydock --token <token> --url <drydock_url> task create -d <design_id> -a prepare_site
$ drydock --token <token> --url <drydock_url> task create -d <design_id> -a prepare_node
$ drydock --token <token> --url <drydock_url> task create -d <design_id> -a deploy_node
# drydock task create -d <design_id> -a verify_site
# drydock task create -d <design_id> -a prepare_site
# drydock task create -d <design_id> -a prepare_node
# drydock task create -d <design_id> -a deploy_node
A demo of this process is available at https://asciinema.org/a/133906

7
drydock_provisioner/cli/action.py

@ -15,13 +15,16 @@
"""
import logging
class CliAction: # pylint: disable=too-few-public-methods
class CliAction: # pylint: disable=too-few-public-methods
""" Action base for CliActions
"""
def __init__(self, api_client):
self.logger = logging.getLogger('drydock_cli')
self.api_client = api_client
self.logger.debug("Action initialized with client %s", self.api_client.session.host)
self.logger.debug("Action initialized with client %s",
self.api_client.session.host)
def invoke(self):
""" The action to be taken. By default, this is not implemented

33
drydock_provisioner/cli/commands.py

@ -24,18 +24,20 @@ from .design import commands as design
from .part import commands as part
from .task import commands as task
@click.group()
@click.option('--debug/--no-debug',
help='Enable or disable debugging',
default=False)
@click.option('--token',
'-t',
help='The auth token to be used',
default=lambda: os.environ.get('DD_TOKEN', ''))
@click.option('--url',
'-u',
help='The url of the running drydock instance',
default=lambda: os.environ.get('DD_URL', ''))
@click.option(
'--debug/--no-debug', help='Enable or disable debugging', default=False)
@click.option(
'--token',
'-t',
help='The auth token to be used',
default=lambda: os.environ.get('DD_TOKEN', ''))
@click.option(
'--url',
'-u',
help='The url of the running drydock instance',
default=lambda: os.environ.get('DD_URL', ''))
@click.pass_context
def drydock(ctx, debug, token, url):
""" Drydock CLI to invoke the running instance of the drydock API
@ -70,9 +72,12 @@ def drydock(ctx, debug, token, url):
logger.debug(url_parse_result)
if not url_parse_result.scheme:
ctx.fail('URL must specify a scheme and hostname, optionally a port')
ctx.obj['CLIENT'] = DrydockClient(DrydockSession(scheme=url_parse_result.scheme,
host=url_parse_result.netloc,
token=token))
ctx.obj['CLIENT'] = DrydockClient(
DrydockSession(
scheme=url_parse_result.scheme,
host=url_parse_result.netloc,
token=token))
drydock.add_command(design.design)
drydock.add_command(part.part)

43
drydock_provisioner/cli/design/actions.py

@ -11,13 +11,13 @@
# 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.
""" Actions related to design
"""
"""Actions related to design."""
from drydock_provisioner.cli.action import CliAction
class DesignList(CliAction): # pylint: disable=too-few-public-methods
""" Action to list designs
"""
class DesignList(CliAction): # pylint: disable=too-few-public-methods
"""Action to list designs."""
def __init__(self, api_client):
super().__init__(api_client)
self.logger.debug("DesignList action initialized")
@ -25,32 +25,39 @@ class DesignList(CliAction): # pylint: disable=too-few-public-methods
def invoke(self):
return self.api_client.get_design_ids()
class DesignCreate(CliAction): # pylint: disable=too-few-public-methods
""" Action to create designs
"""
class DesignCreate(CliAction): # pylint: disable=too-few-public-methods
"""Action to create designs."""
def __init__(self, api_client, base_design=None):
"""
:param string base_design: A UUID of the base design to model after
"""Constructor.
:param string base_design: A UUID of the base design to model after
"""
super().__init__(api_client)
self.logger.debug("DesignCreate action initialized with base_design=%s", base_design)
self.logger.debug(
"DesignCreate action initialized with base_design=%s", base_design)
self.base_design = base_design
def invoke(self):
return self.api_client.create_design(base_design=self.base_design)
class DesignShow(CliAction): # pylint: disable=too-few-public-methods
""" Action to show a design.
:param string design_id: A UUID design_id
:param string source: (Optional) The model source to return. 'designed' is as input,
'compiled' is after merging
class DesignShow(CliAction): # pylint: disable=too-few-public-methods
"""Action to show a design.
:param string design_id: A UUID design_id
:param string source: (Optional) The model source to return. 'designed' is as input,
'compiled' is after merging
"""
def __init__(self, api_client, design_id, source='designed'):
super().__init__(api_client)
self.design_id = design_id
self.source = source
self.logger.debug("DesignShow action initialized for design_id = %s", design_id)
self.logger.debug("DesignShow action initialized for design_id = %s",
design_id)
def invoke(self):
return self.api_client.get_design(design_id=self.design_id, source=self.source)
return self.api_client.get_design(
design_id=self.design_id, source=self.source)

32
drydock_provisioner/cli/design/commands.py

@ -11,8 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" cli.design.commands
Contains commands related to designs
"""cli.design.commands.
Contains commands related to designs
"""
import click
@ -20,37 +21,36 @@ from drydock_provisioner.cli.design.actions import DesignList
from drydock_provisioner.cli.design.actions import DesignShow
from drydock_provisioner.cli.design.actions import DesignCreate
@click.group()
def design():
""" Drydock design commands
"""
"""Drydock design commands."""
pass
@design.command(name='create')
@click.option('--base-design',
'-b',
help='The base design to model this new design after')
@click.option(
'--base-design',
'-b',
help='The base design to model this new design after')
@click.pass_context
def design_create(ctx, base_design=None):
""" Create a design
"""
"""Create a design."""
click.echo(DesignCreate(ctx.obj['CLIENT'], base_design).invoke())
@design.command(name='list')
@click.pass_context
def design_list(ctx):
""" List designs
"""
"""List designs."""
click.echo(DesignList(ctx.obj['CLIENT']).invoke())
@design.command(name='show')
@click.option('--design-id',
'-i',
help='The design id to show')
@click.option('--design-id', '-i', help='The design id to show')
@click.pass_context
def design_show(ctx, design_id):
""" show designs
"""
"""show designs."""
if not design_id:
ctx.fail('The design id must be specified by --design-id')

86
drydock_provisioner/cli/part/actions.py

@ -11,76 +11,82 @@
# 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.
""" Actions related to part command
"""
"""Actions related to part command."""
from drydock_provisioner.cli.action import CliAction
class PartBase(CliAction): # pylint: disable=too-few-public-methods
""" base class to set up part actions requiring a design_id
"""
class PartBase(CliAction): # pylint: disable=too-few-public-methods
"""base class to set up part actions requiring a design_id."""
def __init__(self, api_client, design_id):
super().__init__(api_client)
self.design_id = design_id
self.logger.debug('Initializing a Part action with design_id=%s', design_id)
self.logger.debug('Initializing a Part action with design_id=%s',
design_id)
class PartList(PartBase): # pylint: disable=too-few-public-methods
"""Action to list parts of a design."""
class PartList(PartBase): # pylint: disable=too-few-public-methods
""" Action to list parts of a design
"""
def __init__(self, api_client, design_id):
"""
:param DrydockClient api_client: The api client used for invocation.
:param string design_id: The UUID of the design for which to list parts
"""Constructor.
:param DrydockClient api_client: The api client used for invocation.
:param string design_id: The UUID of the design for which to list parts
"""
super().__init__(api_client, design_id)
self.logger.debug('PartList action initialized')
def invoke(self):
#TODO: change the api call
# TODO(sh8121att): change the api call
return 'This function does not yet have an implementation to support the request'
class PartCreate(PartBase): # pylint: disable=too-few-public-methods
""" Action to create parts of a design
"""
class PartCreate(PartBase): # pylint: disable=too-few-public-methods
"""Action to create parts of a design."""
def __init__(self, api_client, design_id, in_file):
"""
:param DrydockClient api_client: The api client used for invocation.
:param string design_id: The UUID of the design for which to create a part
:param in_file: The file containing the specification of the part
"""Constructor.
:param DrydockClient api_client: The api client used for invocation.
:param string design_id: The UUID of the design for which to create a part
:param in_file: The file containing the specification of the part
"""
super().__init__(api_client, design_id)
self.in_file = in_file
self.logger.debug('PartCreate action init. Input file (trunc to 100 chars)=%s', in_file[:100])
self.logger.debug(
'PartCreate action init. Input file (trunc to 100 chars)=%s',
in_file[:100])
def invoke(self):
return self.api_client.load_parts(self.design_id, self.in_file)
class PartShow(PartBase): # pylint: disable=too-few-public-methods
""" Action to show a part of a design.
"""
class PartShow(PartBase): # pylint: disable=too-few-public-methods
"""Action to show a part of a design."""
def __init__(self, api_client, design_id, kind, key, source='designed'):
"""
:param DrydockClient api_client: The api client used for invocation.
:param string design_id: the UUID of the design containing this part
:param string kind: the string represesnting the 'kind' of the document to return
:param string key: the string representing the key of the document to return.
:param string source: 'designed' (default) if this is the designed version,
'compiled' if the compiled version (after merging)
"""Constructor.
:param DrydockClient api_client: The api client used for invocation.
:param string design_id: the UUID of the design containing this part
:param string kind: the string represesnting the 'kind' of the document to return
:param string key: the string representing the key of the document to return.
:param string source: 'designed' (default) if this is the designed version,
'compiled' if the compiled version (after merging)
"""
super().__init__(api_client, design_id)
self.kind = kind
self.key = key
self.source = source
self.logger.debug('DesignShow action initialized for design_id=%s,'
' kind=%s, key=%s, source=%s',
design_id,
kind,
key,
' kind=%s, key=%s, source=%s', design_id, kind, key,
source)
def invoke(self):
return self.api_client.get_part(design_id=self.design_id,
kind=self.kind,
key=self.key,
source=self.source)
return self.api_client.get_part(
design_id=self.design_id,
kind=self.kind,
key=self.key,
source=self.source)

69
drydock_provisioner/cli/part/commands.py

@ -11,75 +11,76 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" cli.part.commands
Contains commands related to parts of designs
"""cli.part.commands.
Contains commands related to parts of designs.
"""
import click
from drydock_provisioner.cli.part.actions import PartList
from drydock_provisioner.cli.part.actions import PartShow
from drydock_provisioner.cli.part.actions import PartCreate
@click.group()
@click.option('--design-id',
'-d',
help='The id of the design containing the target parts')
@click.option(
'--design-id',
'-d',
help='The id of the design containing the target parts')
@click.pass_context
def part(ctx, design_id=None):
""" Drydock part commands
"""
"""Drydock part commands."""
if not design_id:
ctx.fail('Error: Design id must be specified using --design-id')
ctx.obj['DESIGN_ID'] = design_id
@part.command(name='create')
@click.option('--file',
'-f',
help='The file name containing the part to create')
@click.option(
'--file', '-f', help='The file name containing the part to create')
@click.pass_context
def part_create(ctx, file=None):
""" Create a part
"""
"""Create a part."""
if not file:
ctx.fail('A file to create a part is required using --file')
with open(file, 'r') as file_input:
file_contents = file_input.read()
# here is where some potential validation could be done on the input file
click.echo(PartCreate(ctx.obj['CLIENT'],
design_id=ctx.obj['DESIGN_ID'],
in_file=file_contents).invoke())
click.echo(
PartCreate(
ctx.obj['CLIENT'],
design_id=ctx.obj['DESIGN_ID'],
in_file=file_contents).invoke())
@part.command(name='list')
@click.pass_context
def part_list(ctx):
""" List parts of a design
"""
click.echo(PartList(ctx.obj['CLIENT'], design_id=ctx.obj['DESIGN_ID']).invoke())
"""List parts of a design."""
click.echo(
PartList(ctx.obj['CLIENT'], design_id=ctx.obj['DESIGN_ID']).invoke())
@part.command(name='show')
@click.option('--source',
'-s',
help='designed | compiled')
@click.option('--kind',
'-k',
help='The kind value of the document to show')
@click.option('--key',
'-i',
help='The key value of the document to show')
@click.option('--source', '-s', help='designed | compiled')
@click.option('--kind', '-k', help='The kind value of the document to show')
@click.option('--key', '-i', help='The key value of the document to show')
@click.pass_context
def part_show(ctx, source, kind, key):
""" show a part of a design
"""
"""show a part of a design."""
if not kind:
ctx.fail('The kind must be specified by --kind')
if not key:
ctx.fail('The key must be specified by --key')
click.echo(PartShow(ctx.obj['CLIENT'],
design_id=ctx.obj['DESIGN_ID'],
kind=kind,
key=key,
source=source).invoke())
click.echo(
PartShow(
ctx.obj['CLIENT'],
design_id=ctx.obj['DESIGN_ID'],
kind=kind,
key=key,
source=source).invoke())

42
drydock_provisioner/cli/task/actions.py

@ -16,9 +16,11 @@
from drydock_provisioner.cli.action import CliAction
class TaskList(CliAction): # pylint: disable=too-few-public-methods
class TaskList(CliAction): # pylint: disable=too-few-public-methods
""" Action to list tasks
"""
def __init__(self, api_client):
"""
:param DrydockClient api_client: The api client used for invocation.
@ -29,10 +31,18 @@ class TaskList(CliAction): # pylint: disable=too-few-public-methods
def invoke(self):
return self.api_client.get_tasks()
class TaskCreate(CliAction): # pylint: disable=too-few-public-methods
class TaskCreate(CliAction): # pylint: disable=too-few-public-methods
""" Action to create tasks against a design
"""
def __init__(self, api_client, design_id, action_name=None, node_names=None, rack_names=None, node_tags=None):
def __init__(self,
api_client,
design_id,
action_name=None,
node_names=None,
rack_names=None,
node_tags=None):
"""
:param DrydockClient api_client: The api client used for invocation.
:param string design_id: The UUID of the design for which to create a task
@ -44,7 +54,8 @@ class TaskCreate(CliAction): # pylint: disable=too-few-public-methods
super().__init__(api_client)
self.design_id = design_id
self.action_name = action_name
self.logger.debug('TaskCreate action initialized for design=%s', design_id)
self.logger.debug('TaskCreate action initialized for design=%s',
design_id)
self.logger.debug('Action is %s', action_name)
if node_names is None:
node_names = []
@ -57,19 +68,23 @@ class TaskCreate(CliAction): # pylint: disable=too-few-public-methods
self.logger.debug("Rack names = %s", rack_names)
self.logger.debug("Node tags = %s", node_tags)
self.node_filter = {'node_names' : node_names,
'rack_names' : rack_names,
'node_tags' : node_tags
}
self.node_filter = {
'node_names': node_names,
'rack_names': rack_names,
'node_tags': node_tags
}
def invoke(self):
return self.api_client.create_task(design_id=self.design_id,
task_action=self.action_name,
node_filter=self.node_filter)
return self.api_client.create_task(
design_id=self.design_id,
task_action=self.action_name,
node_filter=self.node_filter)
class TaskShow(CliAction): # pylint: disable=too-few-public-methods
class TaskShow(CliAction): # pylint: disable=too-few-public-methods
""" Action to show a task's detial.
"""
def __init__(self, api_client, task_id):
"""
:param DrydockClient api_client: The api client used for invocation.
@ -77,7 +92,8 @@ class TaskShow(CliAction): # pylint: disable=too-few-public-methods
"""
super().__init__(api_client)
self.task_id = task_id
self.logger.debug('TaskShow action initialized for task_id=%s,', task_id)
self.logger.debug('TaskShow action initialized for task_id=%s,',
task_id)
def invoke(self):
return self.api_client.get_task(task_id=self.task_id)

65
drydock_provisioner/cli/task/commands.py

@ -20,29 +20,35 @@ from drydock_provisioner.cli.task.actions import TaskList
from drydock_provisioner.cli.task.actions import TaskShow
from drydock_provisioner.cli.task.actions import TaskCreate
@click.group()
def task():
""" Drydock task commands
"""
@task.command(name='create')
@click.option('--design-id',
'-d',
help='The design id for this action')
@click.option('--action',
'-a',
help='The action to perform')
@click.option('--node-names',
'-n',
help='The nodes targeted by this action, comma separated')
@click.option('--rack-names',
'-r',
help='The racks targeted by this action, comma separated')
@click.option('--node-tags',
'-t',
help='The nodes by tag name targeted by this action, comma separated')
@click.option('--design-id', '-d', help='The design id for this action')
@click.option('--action', '-a', help='The action to perform')
@click.option(
'--node-names',
'-n',
help='The nodes targeted by this action, comma separated')
@click.option(
'--rack-names',
'-r',
help='The racks targeted by this action, comma separated')
@click.option(
'--node-tags',
'-t',
help='The nodes by tag name targeted by this action, comma separated')
@click.pass_context
def task_create(ctx, design_id=None, action=None, node_names=None, rack_names=None, node_tags=None):
def task_create(ctx,
design_id=None,
action=None,
node_names=None,
rack_names=None,
node_tags=None):
""" Create a task
"""
if not design_id:
@ -51,13 +57,18 @@ def task_create(ctx, design_id=None, action=None, node_names=None, rack_names=No
if not action:
ctx.fail('Error: Action must be specified using --action')
click.echo(TaskCreate(ctx.obj['CLIENT'],
design_id=design_id,
action_name=action,
node_names=[x.strip() for x in node_names.split(',')] if node_names else [],
rack_names=[x.strip() for x in rack_names.split(',')] if rack_names else [],
node_tags=[x.strip() for x in node_tags.split(',')] if node_tags else []
).invoke())
click.echo(
TaskCreate(
ctx.obj['CLIENT'],
design_id=design_id,
action_name=action,
node_names=[x.strip() for x in node_names.split(',')]
if node_names else [],
rack_names=[x.strip() for x in rack_names.split(',')]
if rack_names else [],
node_tags=[x.strip() for x in node_tags.split(',')]
if node_tags else []).invoke())
@task.command(name='list')
@click.pass_context
@ -66,10 +77,9 @@ def task_list(ctx):
"""
click.echo(TaskList(ctx.obj['CLIENT']).invoke())
@task.command(name='show')
@click.option('--task-id',
'-t',
help='The required task id')
@click.option('--task-id', '-t', help='The required task id')
@click.pass_context
def task_show(ctx, task_id=None):
""" show a task's details
@ -77,5 +87,4 @@ def task_show(ctx, task_id=None):
if not task_id:
ctx.fail('The task id must be specified by --task-id')
click.echo(TaskShow(ctx.obj['CLIENT'],
task_id=task_id).invoke())
click.echo(TaskShow(ctx.obj['CLIENT'], task_id=task_id).invoke())

136
drydock_provisioner/config.py

@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Single point of entry to generate the sample configuration file.
This module collects all the necessary info from the other modules in this
@ -40,51 +39,103 @@ import keystoneauth1.loading as loading
IGNORED_MODULES = ('drydock', 'config')
class DrydockConfig(object):
"""
Initialize all the core options
"""
# Default options
options = [
cfg.IntOpt('poll_interval', default=10, help='Polling interval in seconds for checking subtask or downstream status'),
cfg.IntOpt(
'poll_interval',
default=10,
help=
'Polling interval in seconds for checking subtask or downstream status'
),
]
# Logging options
logging_options = [
cfg.StrOpt('log_level', default='INFO', help='Global log level for Drydock'),
cfg.StrOpt('global_logger_name', default='drydock', help='Logger name for the top-level logger'),
cfg.StrOpt('oobdriver_logger_name', default='${global_logger_name}.oobdriver', help='Logger name for OOB driver logging'),
cfg.StrOpt('nodedriver_logger_name', default='${global_logger_name}.nodedriver', help='Logger name for Node driver logging'),
cfg.StrOpt('control_logger_name', default='${global_logger_name}.control', help='Logger name for API server logging'),
cfg.StrOpt(
'log_level', default='INFO', help='Global log level for Drydock'),
cfg.StrOpt(
'global_logger_name',
default='drydock',
help='Logger name for the top-level logger'),
cfg.StrOpt(
'oobdriver_logger_name',
default='${global_logger_name}.oobdriver',
help='Logger name for OOB driver logging'),
cfg.StrOpt(
'nodedriver_logger_name',
default='${global_logger_name}.nodedriver',
help='Logger name for Node driver logging'),
cfg.StrOpt(
'control_logger_name',
default='${global_logger_name}.control',
help='Logger name for API server logging'),
]
# Enabled plugins
plugin_options = [
cfg.MultiStrOpt('ingester',
default=['drydock_provisioner.ingester.plugins.yaml.YamlIngester'],
help='Module path string of a input ingester to enable'),
cfg.MultiStrOpt('oob_driver',
default=['drydock_provisioner.drivers.oob.pyghmi_driver.PyghmiDriver'],
help='Module path string of a OOB driver to enable'),
cfg.StrOpt('node_driver',
default='drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver',
help='Module path string of the Node driver to enable'),
cfg.MultiStrOpt(
'ingester',
default=['drydock_provisioner.ingester.plugins.yaml.YamlIngester'],
help='Module path string of a input ingester to enable'),
cfg.MultiStrOpt(
'oob_driver',
default=[
'drydock_provisioner.drivers.oob.pyghmi_driver.PyghmiDriver'
],
help='Module path string of a OOB driver to enable'),
cfg.StrOpt(
'node_driver',
default=
'drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver',
help='Module path string of the Node driver to enable'),
# TODO Network driver not yet implemented
cfg.StrOpt('network_driver',
default=None,
help='Module path string of the Network driver enable'),
cfg.StrOpt(
'network_driver',
default=None,
help='Module path string of the Network driver enable'),
]
# Timeouts for various tasks specified in minutes
timeout_options = [
cfg.IntOpt('drydock_timeout', default=5, help='Fallback timeout when a specific one is not configured'),
cfg.IntOpt('create_network_template', default=2, help='Timeout in minutes for creating site network templates'),
cfg.IntOpt('configure_user_credentials', default=2, help='Timeout in minutes for creating user credentials'),
cfg.IntOpt('identify_node', default=10, help='Timeout in minutes for initial node identification'),
cfg.IntOpt('configure_hardware', default=30, help='Timeout in minutes for node commissioning and hardware configuration'),
cfg.IntOpt('apply_node_networking', default=5, help='Timeout in minutes for configuring node networking'),
cfg.IntOpt('apply_node_platform', default=5, help='Timeout in minutes for configuring node platform'),
cfg.IntOpt('deploy_node', default=45, help='Timeout in minutes for deploying a node'),
cfg.IntOpt(
'drydock_timeout',
default=5,
help='Fallback timeout when a specific one is not configured'),
cfg.IntOpt(
'create_network_template',
default=2,
help='Timeout in minutes for creating site network templates'),
cfg.IntOpt(
'configure_user_credentials',
default=2,
help='Timeout in minutes for creating user credentials'),
cfg.IntOpt(
'identify_node',
default=10,
help='Timeout in minutes for initial node identification'),
cfg.IntOpt(
'configure_hardware',
default=30,
help=
'Timeout in minutes for node commissioning and hardware configuration'
),
cfg.IntOpt(
'apply_node_networking',
default=5,
help='Timeout in minutes for configuring node networking'),
cfg.IntOpt(
'apply_node_platform',
default=5,
help='Timeout in minutes for configuring node platform'),
cfg.IntOpt(
'deploy_node',
default=45,
help='Timeout in minutes for deploying a node'),
]
def __init__(self):
@ -94,17 +145,23 @@ class DrydockConfig(object):
self.conf.register_opts(DrydockConfig.options)
self.conf.register_opts(DrydockConfig.logging_options, group='logging')
self.conf.register_opts(DrydockConfig.plugin_options, group='plugins')
self.conf.register_opts(DrydockConfig.timeout_options, group='timeouts')
self.conf.register_opts(loading.get_auth_plugin_conf_options('password'), group='keystone_authtoken')
self.conf.register_opts(
DrydockConfig.timeout_options, group='timeouts')
self.conf.register_opts(
loading.get_auth_plugin_conf_options('password'),
group='keystone_authtoken')
config_mgr = DrydockConfig()
def list_opts():
opts = {'DEFAULT': DrydockConfig.options,
'logging': DrydockConfig.logging_options,
'plugins': DrydockConfig.plugin_options,
'timeouts': DrydockConfig.timeout_options
}
opts = {
'DEFAULT': DrydockConfig.options,
'logging': DrydockConfig.logging_options,
'plugins': DrydockConfig.plugin_options,
'timeouts': DrydockConfig.timeout_options
}
package_path = os.path.dirname(os.path.abspath(__file__))
parent_module = ".".join(__name__.split('.')[:-1])
@ -112,13 +169,16 @@ def list_opts():
imported_modules = _import_modules(module_names)
_append_config_options(imported_modules, opts)
# Assume we'll use the password plugin, so include those options in the configuration template
opts['keystone_authtoken'] = loading.get_auth_plugin_conf_options('password')
opts['keystone_authtoken'] = loading.get_auth_plugin_conf_options(
'password')
return _tupleize(opts)
def _tupleize(d):
"""Convert a dict of options to the 2-tuple format."""
return [(key, value) for key, value in d.items()]
def _list_module_names(pkg_path, parent_module):
module_names = []
for _, module_name, ispkg in pkgutil.iter_modules(path=[pkg_path]):
@ -126,11 +186,14 @@ def _list_module_names(pkg_path, parent_module):
# Skip this module.
continue
elif ispkg:
module_names.extend(_list_module_names(pkg_path + "/" + module_name, parent_module + "." + module_name))
module_names.extend(
_list_module_names(pkg_path + "/" + module_name, parent_module
+ "." + module_name))
else:
module_names.append(parent_module + "." + module_name)
return module_names
def _import_modules(module_names):
imported_modules = []
for module_name in module_names:
@ -140,6 +203,7 @@ def _import_modules(module_names):
imported_modules.append(module)
return imported_modules
def _append_config_options(imported_modules, config_options):
for module in imported_modules:
configs = module.list_opts()

34
drydock_provisioner/control/api.py

@ -20,6 +20,7 @@ from .bootdata import *
from .base import DrydockRequest
from .middleware import AuthMiddleware, ContextMiddleware, LoggingMiddleware
def start_api(state_manager=None, ingester=None, orchestrator=None):
"""
Start the Drydock API service
@ -30,24 +31,35 @@ def start_api(state_manager=None, ingester=None, orchestrator=None):
part input
:param orchestrator: Instance of drydock_provisioner.orchestrator.Orchestrator for managing tasks
"""
control_api = falcon.API(request_type=DrydockRequest,
middleware=[AuthMiddleware(), ContextMiddleware(), LoggingMiddleware()])
control_api = falcon.API(
request_type=DrydockRequest,
middleware=[
AuthMiddleware(),
ContextMiddleware(),
LoggingMiddleware()
])
# v1.0 of Drydock API
v1_0_routes = [
# API for managing orchestrator tasks
('/tasks', TasksResource(state_manager=state_manager, orchestrator=orchestrator)),
# API for managing orchestrator tasks
('/tasks', TasksResource(
state_manager=state_manager, orchestrator=orchestrator)),
('/tasks/{task_id}', TaskResource(state_manager=state_manager)),
# API for managing site design data
# API for managing site design data
('/designs', DesignsResource(state_manager=state_manager)),
('/designs/{design_id}', DesignResource(state_manager=state_manager, orchestrator=orchestrator)),
('/designs/{design_id}/parts', DesignsPartsResource(state_manager=state_manager, ingester=ingester)),
('/designs/{design_id}/parts/{kind}', DesignsPartsKindsResource(state_manager=state_manager)),
('/designs/{design_id}/parts/{kind}/{name}', DesignsPartResource(state_manager=state_manager, orchestrator=orchestrator)),
('/designs/{design_id}', DesignResource(
state_manager=state_manager, orchestrator=orchestrator)),
('/designs/{design_id}/parts', DesignsPartsResource(
state_manager=state_manager, ingester=ingester)),
('/designs/{design_id}/parts/{kind}', DesignsPartsKindsResource(
state_manager=state_manager)),
('/designs/{design_id}/parts/{kind}/{name}', DesignsPartResource(
state_manager=state_manager, orchestrator=orchestrator)),
# API for nodes to discover their bootdata during curtin install
('/bootdata/{hostname}/{data_key}', BootdataResource(state_manager=state_manager, orchestrator=orchestrator))
# API for nodes to discover their bootdata during curtin install
('/bootdata/{hostname}/{data_key}', BootdataResource(
state_manager=state_manager, orchestrator=orchestrator))
]
for path, res in v1_0_routes:

43
drydock_provisioner/control/base.py

@ -20,8 +20,8 @@ import falcon.request
import drydock_provisioner.error as errors
class BaseResource(object):
class BaseResource(object):
def __init__(self):
self.logger = logging.getLogger('control')
@ -41,7 +41,8 @@ class BaseResource(object):
if req.content_length is None or req.content_length == 0:
return None
if req.content_type is not None and req.content_type.lower() == 'application/json':
if req.content_type is not None and req.content_type.lower(
) == 'application/json':
raw_body = req.stream.read(req.content_length or 0)
if raw_body is None:
@ -51,20 +52,23 @@ class BaseResource(object):
json_body = json.loads(raw_body.decode('utf-8'))
return json_body
except json.JSONDecodeError as jex:
raise errors.InvalidFormat("%s: Invalid JSON in body: %s" % (req.path, jex))
print("Invalid JSON in request: \n%s" % raw_body.decode('utf-8'))
self.error(req.context, "Invalid JSON in request: \n%s" % raw_body.decode('utf-8'))
raise errors.InvalidFormat("%s: Invalid JSON in body: %s" %
(req.path, jex))
else:
raise errors.InvalidFormat("Requires application/json payload")
def return_error(self, resp, status_code, message="", retry=False):
resp.body = json.dumps({'type': 'error', 'message': message, 'retry': retry})
resp.body = json.dumps({
'type': 'error',
'message': message,
'retry': retry
})
resp.status = status_code
def log_error(self, ctx, level, msg):
extra = {
'user': 'N/A',
'req_id': 'N/A',
'external_ctx': 'N/A'
}
extra = {'user': 'N/A', 'req_id': 'N/A', 'external_ctx': 'N/A'}
if ctx is not None:
extra = {
@ -89,27 +93,29 @@ class BaseResource(object):
class StatefulResource(BaseResource):
def __init__(self, state_manager=None, **kwargs):
super(StatefulResource, self).__init__(**kwargs)
if state_manager is None:
self.error(None, "StatefulResource:init - StatefulResources require a state manager be set")
raise ValueError("StatefulResources require a state manager be set")
self.error(
None,
"StatefulResource:init - StatefulResources require a state manager be set"
)
raise ValueError(
"StatefulResources require a state manager be set")
self.state_manager = state_manager
class DrydockRequestContext(object):
def __init__(self):
self.log_level = 'ERROR'
self.user = None # Username
self.user_id = None # User ID (UUID)
self.user_domain_id = None # Domain owning user
self.user = None # Username
self.user_id = None # User ID (UUID)
self.user_domain_id = None # Domain owning user
self.roles = []
self.project_id = None
self.project_domain_id = None # Domain owning project
self.project_domain_id = None # Domain owning project
self.is_admin_project = False
self.authenticated = False
self.request_id = str(uuid.uuid4())
@ -133,8 +139,7 @@ class DrydockRequestContext(object):
self.roles.extend(roles)
def remove_role(self, role):
self.roles = [x for x in self.roles
if x != role]
self.roles = [x for x in self.roles if x != role]
def set_external_marker(self, marker):
self.external_marker = marker

22
drydock_provisioner/control/bootdata.py

@ -20,10 +20,14 @@ from oslo_config import cfg
from .base import StatefulResource
class BootdataResource(StatefulResource):
bootdata_options = [
cfg.StrOpt('prom_init', default='/etc/drydock/bootdata/join.sh', help='Path to file to distribute for prom_init.sh')
cfg.StrOpt(
'prom_init',
default='/etc/drydock/bootdata/join.sh',
help='Path to file to distribute for prom_init.sh')
]
def __init__(self, orchestrator=None, **kwargs):
@ -31,7 +35,8 @@ class BootdataResource(StatefulResource):
self.authorized_roles = ['anyone']
self.orchestrator = orchestrator
cfg.CONF.register_opts(BootdataResource.bootdata_options, group='bootdata')
cfg.CONF.register_opts(
BootdataResource.bootdata_options, group='bootdata')
init_file = open(cfg.CONF.bootdata.prom_init, 'r')
self.prom_init = init_file.read()
@ -39,7 +44,7 @@ class BootdataResource(StatefulResource):
def on_get(self, req, resp, hostname, data_key):
if data_key == 'promservice':
resp.body = BootdataResource.prom_init_service
resp.body = BootdataResource.prom_init_service
resp.content_type = 'text/plain'
return
elif data_key == 'vfservice':
@ -60,7 +65,8 @@ class BootdataResource(StatefulResource):
resp.content_type = 'text/plain'
host_design_id = bootdata.get('design_id', None)
host_design = self.orchestrator.get_effective_site(host_design_id)
host_design = self.orchestrator.get_effective_site(
host_design_id)
host_model = host_design.get_baremetal_node(hostname)
@ -71,9 +77,12 @@ class BootdataResource(StatefulResource):
all_configs = host_design.get_promenade_config(part_selectors)
part_list = [i.document for i in all_configs]
part_list = [i.document for i in all_configs]
resp.body = "---\n" + "---\n".join([base64.b64decode(i.encode()).decode('utf-8') for i in part_list]) + "\n..."
resp.body = "---\n" + "---\n".join([
base64.b64decode(i.encode()).decode('utf-8')
for i in part_list
]) + "\n..."
return
@ -106,5 +115,6 @@ ExecStart=/bin/sh -c '/bin/echo 4 >/sys/class/net/ens3f0/device/sriov_numvfs'
WantedBy=multi-user.target
"""
def list_opts():
return {'bootdata': BootdataResource.bootdata_options}

123
drydock_provisioner/control/designs.py

@ -21,8 +21,8 @@ import drydock_provisioner.error as errors
from .base import StatefulResource
class DesignsResource(StatefulResource):
class DesignsResource(StatefulResource):
def __init__(self, **kwargs):
super(DesignsResource, self).__init__(**kwargs)
@ -38,7 +38,11 @@ class DesignsResource(StatefulResource):
resp.status = falcon.HTTP_200
except Exception as ex:
self.error(req.context, "Exception raised: %s" % str(ex))
self.return_error(resp, falcon.HTTP_500, message="Error accessing design list", retry=True)
self.return_error(
resp,
falcon.HTTP_500,
message="Error accessing design list",
retry=True)
@policy.ApiEnforcer('physical_provisioner:ingest_data')
def on_post(self, req, resp):
@ -52,7 +56,8 @@ class DesignsResource(StatefulResource):
if base_design is not None:
base_design = uuid.UUID(base_design)
design = hd_objects.SiteDesign(base_design_id=base_design_uuid)
design = hd_objects.SiteDesign(
base_design_id=base_design_uuid)
else:
design = hd_objects.SiteDesign()
design.assign_id()
@ -62,14 +67,18 @@ class DesignsResource(StatefulResource):
resp.status = falcon.HTTP_201
except errors.StateError as stex:
self.error(req.context, "Error updating persistence")
self.return_error(resp, falcon.HTTP_500, message="Error updating persistence", retry=True)
self.return_error(
resp,
falcon.HTTP_500,
message="Error updating persistence",
retry=True)
except errors.InvalidFormat as fex:
self.error(req.context, str(fex))
self.return_error(resp, falcon.HTTP_400, message=str(fex), retry=False)
self.return_error(
resp, falcon.HTTP_400, message=str(fex), retry=False)
class DesignResource(StatefulResource):
def __init__(self, orchestrator=None, **kwargs):
super(DesignResource, self).__init__(**kwargs)
self.authorized_roles = ['user']
@ -90,47 +99,81 @@ class DesignResource(StatefulResource):
resp.body = json.dumps(design.obj_to_simple())
except errors.DesignError:
self.error(req.context, "Design %s not found" % design_id)
self.return_error(resp, falcon.HTTP_404, message="Design %s not found" % design_id, retry=False)
self.return_error(
resp,
falcon.HTTP_404,
message="Design %s not found" % design_id,
retry=False)
class DesignsPartsResource(StatefulResource):
class DesignsPartsResource(StatefulResource):
def __init__(self, ingester=None, **kwargs):
super(DesignsPartsResource, self).__init__(**kwargs)
self.ingester = ingester
self.authorized_roles = ['user']
if ingester is None:
self.error(None, "DesignsPartsResource requires a configured Ingester instance")
raise ValueError("DesignsPartsResource requires a configured Ingester instance")
self.error(
None,
"DesignsPartsResource requires a configured Ingester instance")
raise ValueError(
"DesignsPartsResource requires a configured Ingester instance")
@policy.ApiEnforcer('physical_provisioner:ingest_data')
def on_post(self, req, resp, design_id):
ingester_name = req.params.get('ingester', None)
if ingester_name is None:
self.error(None, "DesignsPartsResource POST requires parameter 'ingester'")
self.return_error(resp, falcon.HTTP_400, message="POST requires parameter 'ingester'", retry=False)
self.error(
None,
"DesignsPartsResource POST requires parameter 'ingester'")
self.return_error(
resp,
falcon.HTTP_400,
message="POST requires parameter 'ingester'",
retry=False)
else:
try:
raw_body = req.stream.read(req.content_length or 0)
if raw_body is not None and len(raw_body) > 0:
parsed_items = self.ingester.ingest_data(plugin_name=ingester_name, design_state=self.state_manager,
content=raw_body, design_id=design_id, context=req.context)
parsed_items = self.ingester.ingest_data(
plugin_name=ingester_name,
design_state=self.state_manager,
content=raw_body,
design_id=design_id,
context=req.context)
resp.status = falcon.HTTP_201
resp.body = json.dumps([x.obj_to_simple() for x in parsed_items])
resp.body = json.dumps(
[x.obj_to_simple() for x in parsed_items])
else:
self.return_error(resp, falcon.HTTP_400, message="Empty body not supported", retry=False)
self.return_error(
resp,
falcon.HTTP_400,
message="Empty body not supported",
retry=False)
except ValueError:
self.return_error(resp, falcon.HTTP_500, message="Error processing input", retry=False)
self.return_error(
resp,
falcon.HTTP_500,
message="Error processing input",
retry=False)
except LookupError:
self.return_error(resp, falcon.HTTP_400, message="Ingester %s not registered" % ingester_name, retry=False)
self.return_error(
resp,
falcon.HTTP_400,
message="Ingester %s not registered" % ingester_name,
retry=False)
@policy.ApiEnforcer('physical_provisioner:ingest_data')
def on_get(self, req, resp, design_id):
try:
design = self.state_manager.get_design(design_id)
except DesignError:
self.return_error(resp, falcon.HTTP_404, message="Design %s nout found" % design_id, retry=False)
self.return_error(
resp,
falcon.HTTP_404,
message="Design %s nout found" % design_id,
retry=False)
part_catalog = []
@ -138,15 +181,30 @@ class DesignsPartsResource(StatefulResource):
part_catalog.append({'kind': 'Region', 'key': site.get_id()})
part_catalog.extend([{'kind': 'Network', 'key': n.get_id()} for n in design.networks])
part_catalog.extend([{
'kind': 'Network',
'key': n.get_id()
} for n in design.networks])
part_catalog.extend([{'kind': 'NetworkLink', 'key': l.get_id()} for l in design<