From 87a2257477619407cc2d84c21549668b2775b358 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Fri, 27 Jun 2014 18:07:43 +0900 Subject: [PATCH] implement servicevm related command Change-Id: Ib606eb3e4721ec9a255092bf4581f3b576c3714a --- {neutronclient => tackerclient}/shell.py | 270 ++---------- tackerclient/tacker/v1_0/vm/__init__.py | 0 tackerclient/tacker/v1_0/vm/device.py | 137 ++++++ .../tacker/v1_0/vm/device_template.py | 94 ++++ .../tacker/v1_0/vm/service_instance.py | 164 +++++++ .../unit/{test_cli20.py => test_cli10.py} | 0 ...extensions.py => test_cli10_extensions.py} | 0 tackerclient/tests/unit/vm/__init__.py | 0 .../tests/unit/vm/test_cli10_device.py | 126 ++++++ .../unit/vm/test_cli10_device_template.py | 132 ++++++ .../unit/vm/test_cli10_service_instance.py | 155 +++++++ tackerclient/v1_0/client.py | 411 ++++++++++++++++++ 12 files changed, 1260 insertions(+), 229 deletions(-) rename {neutronclient => tackerclient}/shell.py (59%) create mode 100644 tackerclient/tacker/v1_0/vm/__init__.py create mode 100644 tackerclient/tacker/v1_0/vm/device.py create mode 100644 tackerclient/tacker/v1_0/vm/device_template.py create mode 100644 tackerclient/tacker/v1_0/vm/service_instance.py rename tackerclient/tests/unit/{test_cli20.py => test_cli10.py} (100%) rename tackerclient/tests/unit/{test_cli20_extensions.py => test_cli10_extensions.py} (100%) create mode 100644 tackerclient/tests/unit/vm/__init__.py create mode 100644 tackerclient/tests/unit/vm/test_cli10_device.py create mode 100644 tackerclient/tests/unit/vm/test_cli10_device_template.py create mode 100644 tackerclient/tests/unit/vm/test_cli10_service_instance.py create mode 100644 tackerclient/v1_0/client.py diff --git a/neutronclient/shell.py b/tackerclient/shell.py similarity index 59% rename from neutronclient/shell.py rename to tackerclient/shell.py index 31e23193..9f6ab0fa 100644 --- a/neutronclient/shell.py +++ b/tackerclient/shell.py @@ -15,7 +15,7 @@ # """ -Command-line interface to the Neutron APIs +Command-line interface to the Tacker APIs """ from __future__ import print_function @@ -28,46 +28,20 @@ import sys from cliff import app from cliff import commandmanager -from neutronclient.common import clientmanager -from neutronclient.common import exceptions as exc -from neutronclient.common import utils -from neutronclient.neutron.v2_0 import agent -from neutronclient.neutron.v2_0 import agentscheduler -from neutronclient.neutron.v2_0 import credential -from neutronclient.neutron.v2_0 import extension -from neutronclient.neutron.v2_0 import floatingip -from neutronclient.neutron.v2_0.fw import firewall -from neutronclient.neutron.v2_0.fw import firewallpolicy -from neutronclient.neutron.v2_0.fw import firewallrule -from neutronclient.neutron.v2_0.lb import healthmonitor as lb_healthmonitor -from neutronclient.neutron.v2_0.lb import member as lb_member -from neutronclient.neutron.v2_0.lb import pool as lb_pool -from neutronclient.neutron.v2_0.lb import vip as lb_vip -from neutronclient.neutron.v2_0 import metering -from neutronclient.neutron.v2_0.nec import packetfilter -from neutronclient.neutron.v2_0 import netpartition -from neutronclient.neutron.v2_0 import network -from neutronclient.neutron.v2_0 import networkprofile -from neutronclient.neutron.v2_0.nsx import networkgateway -from neutronclient.neutron.v2_0.nsx import qos_queue -from neutronclient.neutron.v2_0 import policyprofile -from neutronclient.neutron.v2_0 import port -from neutronclient.neutron.v2_0 import quota -from neutronclient.neutron.v2_0 import router -from neutronclient.neutron.v2_0 import securitygroup -from neutronclient.neutron.v2_0 import servicetype -from neutronclient.neutron.v2_0 import subnet -from neutronclient.neutron.v2_0.vpn import ikepolicy -from neutronclient.neutron.v2_0.vpn import ipsec_site_connection -from neutronclient.neutron.v2_0.vpn import ipsecpolicy -from neutronclient.neutron.v2_0.vpn import vpnservice -from neutronclient.openstack.common.gettextutils import _ -from neutronclient.openstack.common import strutils -from neutronclient.version import __version__ +from tackerclient.common import clientmanager +from tackerclient.common import exceptions as exc +from tackerclient.common import utils +from tackerclient.openstack.common.gettextutils import _ +from tackerclient.openstack.common import strutils +from tackerclient.tacker.v1_0 import extension +from tackerclient.tacker.v1_0.vm import device +from tackerclient.tacker.v1_0.vm import device_template +from tackerclient.tacker.v1_0.vm import service_instance +from tackerclient.version import __version__ -VERSION = '2.0' -NEUTRON_API_VERSION = '2.0' +VERSION = '1.0' +TACKER_API_VERSION = '1.0' def run_command(cmd, cmd_parser, sub_argv): @@ -97,189 +71,27 @@ def env(*_vars, **kwargs): return kwargs.get('default', '') -COMMAND_V2 = { - 'net-list': network.ListNetwork, - 'net-external-list': network.ListExternalNetwork, - 'net-show': network.ShowNetwork, - 'net-create': network.CreateNetwork, - 'net-delete': network.DeleteNetwork, - 'net-update': network.UpdateNetwork, - 'subnet-list': subnet.ListSubnet, - 'subnet-show': subnet.ShowSubnet, - 'subnet-create': subnet.CreateSubnet, - 'subnet-delete': subnet.DeleteSubnet, - 'subnet-update': subnet.UpdateSubnet, - 'port-list': port.ListPort, - 'port-show': port.ShowPort, - 'port-create': port.CreatePort, - 'port-delete': port.DeletePort, - 'port-update': port.UpdatePort, - 'quota-list': quota.ListQuota, - 'quota-show': quota.ShowQuota, - 'quota-delete': quota.DeleteQuota, - 'quota-update': quota.UpdateQuota, +COMMAND_V1 = { 'ext-list': extension.ListExt, 'ext-show': extension.ShowExt, - 'router-list': router.ListRouter, - 'router-port-list': port.ListRouterPort, - 'router-show': router.ShowRouter, - 'router-create': router.CreateRouter, - 'router-delete': router.DeleteRouter, - 'router-update': router.UpdateRouter, - 'router-interface-add': router.AddInterfaceRouter, - 'router-interface-delete': router.RemoveInterfaceRouter, - 'router-gateway-set': router.SetGatewayRouter, - 'router-gateway-clear': router.RemoveGatewayRouter, - 'floatingip-list': floatingip.ListFloatingIP, - 'floatingip-show': floatingip.ShowFloatingIP, - 'floatingip-create': floatingip.CreateFloatingIP, - 'floatingip-delete': floatingip.DeleteFloatingIP, - 'floatingip-associate': floatingip.AssociateFloatingIP, - 'floatingip-disassociate': floatingip.DisassociateFloatingIP, - 'security-group-list': securitygroup.ListSecurityGroup, - 'security-group-show': securitygroup.ShowSecurityGroup, - 'security-group-create': securitygroup.CreateSecurityGroup, - 'security-group-delete': securitygroup.DeleteSecurityGroup, - 'security-group-update': securitygroup.UpdateSecurityGroup, - 'security-group-rule-list': securitygroup.ListSecurityGroupRule, - 'security-group-rule-show': securitygroup.ShowSecurityGroupRule, - 'security-group-rule-create': securitygroup.CreateSecurityGroupRule, - 'security-group-rule-delete': securitygroup.DeleteSecurityGroupRule, - 'lb-vip-list': lb_vip.ListVip, - 'lb-vip-show': lb_vip.ShowVip, - 'lb-vip-create': lb_vip.CreateVip, - 'lb-vip-update': lb_vip.UpdateVip, - 'lb-vip-delete': lb_vip.DeleteVip, - 'lb-pool-list': lb_pool.ListPool, - 'lb-pool-show': lb_pool.ShowPool, - 'lb-pool-create': lb_pool.CreatePool, - 'lb-pool-update': lb_pool.UpdatePool, - 'lb-pool-delete': lb_pool.DeletePool, - 'lb-pool-stats': lb_pool.RetrievePoolStats, - 'lb-member-list': lb_member.ListMember, - 'lb-member-show': lb_member.ShowMember, - 'lb-member-create': lb_member.CreateMember, - 'lb-member-update': lb_member.UpdateMember, - 'lb-member-delete': lb_member.DeleteMember, - 'lb-healthmonitor-list': lb_healthmonitor.ListHealthMonitor, - 'lb-healthmonitor-show': lb_healthmonitor.ShowHealthMonitor, - 'lb-healthmonitor-create': lb_healthmonitor.CreateHealthMonitor, - 'lb-healthmonitor-update': lb_healthmonitor.UpdateHealthMonitor, - 'lb-healthmonitor-delete': lb_healthmonitor.DeleteHealthMonitor, - 'lb-healthmonitor-associate': lb_healthmonitor.AssociateHealthMonitor, - 'lb-healthmonitor-disassociate': ( - lb_healthmonitor.DisassociateHealthMonitor - ), - 'queue-create': qos_queue.CreateQoSQueue, - 'queue-delete': qos_queue.DeleteQoSQueue, - 'queue-show': qos_queue.ShowQoSQueue, - 'queue-list': qos_queue.ListQoSQueue, - 'agent-list': agent.ListAgent, - 'agent-show': agent.ShowAgent, - 'agent-delete': agent.DeleteAgent, - 'agent-update': agent.UpdateAgent, - 'net-gateway-create': networkgateway.CreateNetworkGateway, - 'net-gateway-update': networkgateway.UpdateNetworkGateway, - 'net-gateway-delete': networkgateway.DeleteNetworkGateway, - 'net-gateway-show': networkgateway.ShowNetworkGateway, - 'net-gateway-list': networkgateway.ListNetworkGateway, - 'net-gateway-connect': networkgateway.ConnectNetworkGateway, - 'net-gateway-disconnect': networkgateway.DisconnectNetworkGateway, - 'gateway-device-create': networkgateway.CreateGatewayDevice, - 'gateway-device-update': networkgateway.UpdateGatewayDevice, - 'gateway-device-delete': networkgateway.DeleteGatewayDevice, - 'gateway-device-show': networkgateway.ShowGatewayDevice, - 'gateway-device-list': networkgateway.ListGatewayDevice, - 'dhcp-agent-network-add': agentscheduler.AddNetworkToDhcpAgent, - 'dhcp-agent-network-remove': agentscheduler.RemoveNetworkFromDhcpAgent, - 'net-list-on-dhcp-agent': agentscheduler.ListNetworksOnDhcpAgent, - 'dhcp-agent-list-hosting-net': agentscheduler.ListDhcpAgentsHostingNetwork, - 'l3-agent-router-add': agentscheduler.AddRouterToL3Agent, - 'l3-agent-router-remove': agentscheduler.RemoveRouterFromL3Agent, - 'router-list-on-l3-agent': agentscheduler.ListRoutersOnL3Agent, - 'l3-agent-list-hosting-router': agentscheduler.ListL3AgentsHostingRouter, - 'lb-pool-list-on-agent': agentscheduler.ListPoolsOnLbaasAgent, - 'lb-agent-hosting-pool': agentscheduler.GetLbaasAgentHostingPool, - 'service-provider-list': servicetype.ListServiceProvider, - 'firewall-rule-list': firewallrule.ListFirewallRule, - 'firewall-rule-show': firewallrule.ShowFirewallRule, - 'firewall-rule-create': firewallrule.CreateFirewallRule, - 'firewall-rule-update': firewallrule.UpdateFirewallRule, - 'firewall-rule-delete': firewallrule.DeleteFirewallRule, - 'firewall-policy-list': firewallpolicy.ListFirewallPolicy, - 'firewall-policy-show': firewallpolicy.ShowFirewallPolicy, - 'firewall-policy-create': firewallpolicy.CreateFirewallPolicy, - 'firewall-policy-update': firewallpolicy.UpdateFirewallPolicy, - 'firewall-policy-delete': firewallpolicy.DeleteFirewallPolicy, - 'firewall-policy-insert-rule': firewallpolicy.FirewallPolicyInsertRule, - 'firewall-policy-remove-rule': firewallpolicy.FirewallPolicyRemoveRule, - 'firewall-list': firewall.ListFirewall, - 'firewall-show': firewall.ShowFirewall, - 'firewall-create': firewall.CreateFirewall, - 'firewall-update': firewall.UpdateFirewall, - 'firewall-delete': firewall.DeleteFirewall, - 'cisco-credential-list': credential.ListCredential, - 'cisco-credential-show': credential.ShowCredential, - 'cisco-credential-create': credential.CreateCredential, - 'cisco-credential-delete': credential.DeleteCredential, - 'cisco-network-profile-list': networkprofile.ListNetworkProfile, - 'cisco-network-profile-show': networkprofile.ShowNetworkProfile, - 'cisco-network-profile-create': networkprofile.CreateNetworkProfile, - 'cisco-network-profile-delete': networkprofile.DeleteNetworkProfile, - 'cisco-network-profile-update': networkprofile.UpdateNetworkProfile, - 'cisco-policy-profile-list': policyprofile.ListPolicyProfile, - 'cisco-policy-profile-show': policyprofile.ShowPolicyProfile, - 'cisco-policy-profile-update': policyprofile.UpdatePolicyProfile, - 'ipsec-site-connection-list': ( - ipsec_site_connection.ListIPsecSiteConnection - ), - 'ipsec-site-connection-show': ( - ipsec_site_connection.ShowIPsecSiteConnection - ), - 'ipsec-site-connection-create': ( - ipsec_site_connection.CreateIPsecSiteConnection - ), - 'ipsec-site-connection-update': ( - ipsec_site_connection.UpdateIPsecSiteConnection - ), - 'ipsec-site-connection-delete': ( - ipsec_site_connection.DeleteIPsecSiteConnection - ), - 'vpn-service-list': vpnservice.ListVPNService, - 'vpn-service-show': vpnservice.ShowVPNService, - 'vpn-service-create': vpnservice.CreateVPNService, - 'vpn-service-update': vpnservice.UpdateVPNService, - 'vpn-service-delete': vpnservice.DeleteVPNService, - 'vpn-ipsecpolicy-list': ipsecpolicy.ListIPsecPolicy, - 'vpn-ipsecpolicy-show': ipsecpolicy.ShowIPsecPolicy, - 'vpn-ipsecpolicy-create': ipsecpolicy.CreateIPsecPolicy, - 'vpn-ipsecpolicy-update': ipsecpolicy.UpdateIPsecPolicy, - 'vpn-ipsecpolicy-delete': ipsecpolicy.DeleteIPsecPolicy, - 'vpn-ikepolicy-list': ikepolicy.ListIKEPolicy, - 'vpn-ikepolicy-show': ikepolicy.ShowIKEPolicy, - 'vpn-ikepolicy-create': ikepolicy.CreateIKEPolicy, - 'vpn-ikepolicy-update': ikepolicy.UpdateIKEPolicy, - 'vpn-ikepolicy-delete': ikepolicy.DeleteIKEPolicy, - 'meter-label-create': metering.CreateMeteringLabel, - 'meter-label-list': metering.ListMeteringLabel, - 'meter-label-show': metering.ShowMeteringLabel, - 'meter-label-delete': metering.DeleteMeteringLabel, - 'meter-label-rule-create': metering.CreateMeteringLabelRule, - 'meter-label-rule-list': metering.ListMeteringLabelRule, - 'meter-label-rule-show': metering.ShowMeteringLabelRule, - 'meter-label-rule-delete': metering.DeleteMeteringLabelRule, - 'nuage-netpartition-list': netpartition.ListNetPartition, - 'nuage-netpartition-show': netpartition.ShowNetPartition, - 'nuage-netpartition-create': netpartition.CreateNetPartition, - 'nuage-netpartition-delete': netpartition.DeleteNetPartition, - 'nec-packet-filter-list': packetfilter.ListPacketFilter, - 'nec-packet-filter-show': packetfilter.ShowPacketFilter, - 'nec-packet-filter-create': packetfilter.CreatePacketFilter, - 'nec-packet-filter-update': packetfilter.UpdatePacketFilter, - 'nec-packet-filter-delete': packetfilter.DeletePacketFilter, + 'device-template-create': device_template.CreateDeviceTemplate, + 'device-template-list': device_template.ListDeviceTemplate, + 'device-template-show': device_template.ShowDeviceTemplate, + 'device-template-update': device_template.UpdateDeviceTemplate, + 'device-template-delete': device_template.DeleteDeviceTemplate, + 'service-instance-create': service_instance.CreateServiceInstance, + 'service-instance-list': service_instance.ListServiceInstance, + 'service-instance-show': service_instance.ShowServiceInstance, + 'service-instance-update': service_instance.UpdateServiceInstance, + 'service-instance-delete': service_instance.DeleteServiceInstance, + 'device-create': device.CreateDevice, + 'device-list': device.ListDevice, + 'device-show': device.ShowDevice, + 'device-update': device.UpdateDevice, + 'device-delete': device.DeleteDevice, } -COMMANDS = {'2.0': COMMAND_V2} +COMMANDS = {'1.0': COMMAND_V1} class HelpAction(argparse.Action): @@ -307,7 +119,7 @@ class HelpAction(argparse.Action): sys.exit(0) -class NeutronShell(app.App): +class TackerShell(app.App): # verbose logging levels WARNING_LEVEL = 0 @@ -318,10 +130,10 @@ class NeutronShell(app.App): log = logging.getLogger(__name__) def __init__(self, apiversion): - super(NeutronShell, self).__init__( + super(TackerShell, self).__init__( description=__doc__.strip(), version=VERSION, - command_manager=commandmanager.CommandManager('neutron.cli'), ) + command_manager=commandmanager.CommandManager('tacker.cli'), ) self.commands = COMMANDS for k, v in self.commands[apiversion].items(): self.command_manager.add_command(k, v) @@ -373,8 +185,8 @@ class NeutronShell(app.App): '--os-auth-strategy', metavar='', default=env('OS_AUTH_STRATEGY', default='keystone'), help=_('Authentication strategy (Env: OS_AUTH_STRATEGY' - ', default keystone). For now, any other value will' - ' disable the authentication')) + ', default keystone). For now, any other value will' + ' disable the authentication')) parser.add_argument( '--os_auth_strategy', help=argparse.SUPPRESS) @@ -466,8 +278,8 @@ class NeutronShell(app.App): parser.add_argument( '--insecure', action='store_true', - default=env('NEUTRONCLIENT_INSECURE', default=False), - help=_("Explicitly allow neutronclient to perform \"insecure\" " + default=env('TACKERCLIENT_INSECURE', default=False), + help=_("Explicitly allow tackerclient to perform \"insecure\" " "SSL (https) requests. The server's certificate will " "not be verified against any certificate authorities. " "This option should be used with caution.")) @@ -650,7 +462,7 @@ class NeutronShell(app.App): * validate authentication info """ - super(NeutronShell, self).initialize_app(argv) + super(TackerShell, self).initialize_app(argv) self.api_version = {'network': self.api_version} @@ -693,9 +505,9 @@ class NeutronShell(app.App): def main(argv=sys.argv[1:]): try: - return NeutronShell(NEUTRON_API_VERSION).run(map(strutils.safe_decode, - argv)) - except exc.NeutronClientException: + return TackerShell(TACKER_API_VERSION).run(map(strutils.safe_decode, + argv)) + except exc.TackerClientException: return 1 except Exception as e: print(unicode(e)) diff --git a/tackerclient/tacker/v1_0/vm/__init__.py b/tackerclient/tacker/v1_0/vm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/tacker/v1_0/vm/device.py b/tackerclient/tacker/v1_0/vm/device.py new file mode 100644 index 00000000..55e42757 --- /dev/null +++ b/tackerclient/tacker/v1_0/vm/device.py @@ -0,0 +1,137 @@ +# +# Copyright 2013 Intel +# Copyright 2013 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel + +from tackerclient.common import exceptions +from tackerclient.openstack.common.gettextutils import _ +from tackerclient.tacker import v1_0 as tackerV10 + + +_DEVICE = 'device' + + +class ListDevice(tackerV10.ListCommand): + """List device that belong to a given tenant.""" + + resource = _DEVICE + + +class ShowDevice(tackerV10.ShowCommand): + """show information of a given Device.""" + + resource = _DEVICE + + +class CreateDevice(tackerV10.CreateCommand): + """create a Device.""" + + resource = _DEVICE + + def add_known_arguments(self, parser): + parser.add_argument( + '--device-template-id', + required=True, + help='device template id to create device based on') + parser.add_argument( + '--kwargs', + metavar='=', + action='append', + dest='kwargs', + default=[], + help='instance specific argument') + parser.add_argument( + '--service-context', + metavar='', + action='append', + dest='service_context', + default=[], + help='service context to insert service') + + def args2body(self, parsed_args): + body = { + self.resource: { + 'template_id': parsed_args.device_template_id, + } + } + if parsed_args.kwargs: + try: + kwargs = dict(key_value.split('=', 1) + for key_value in parsed_args.kwargs) + except ValueError: + msg = (_('invalid argument for --kwargs %s') % + parsed_args.kwargs) + raise exceptions.TackerCLIError(msg) + if kwargs: + body[self.resource]['kwargs'] = kwargs + if parsed_args.service_context: + try: + service_context = [dict( + (k.replace('-', '_'), v) + for k, v in (key_value.split('=', 1) + for key_value in entry_string.split(','))) + for entry_string in parsed_args.service_context] + except ValueError: + msg = (_('invalid argument for --service-context %s') % + parsed_args.service_context) + raise exceptions.TackerCLIError(msg) + + if service_context: + body[self.resource]['service_context'] = service_context + + tackerV10.update_dict(parsed_args, body[self.resource], ['tenant_id']) + return body + + +class UpdateDevice(tackerV10.UpdateCommand): + """Update a given Device.""" + + resource = _DEVICE + + def add_known_arguments(self, parser): + parser.add_argument( + '--kwargs', + metavar='=', + action='append', + dest='kwargs', + default=[], + help='instance specific argument') + + def args2body(self, parsed_args): + body = {self.resource: {}} + if parsed_args.kwargs: + try: + kwargs = dict(key_value.split('=', 1) + for key_value in parsed_args.kwargs) + except ValueError: + msg = (_('invalid argument for --kwargs %s') % + parsed_args.kwargs) + raise exceptions.TackerCLIError(msg) + if kwargs: + body[self.resource]['kwargs'] = kwargs + tackerV10.update_dict(parsed_args, body[self.resource], ['tenant_id']) + return body + + +class DeleteDevice(tackerV10.DeleteCommand): + """Delete a given Device.""" + + resource = _DEVICE diff --git a/tackerclient/tacker/v1_0/vm/device_template.py b/tackerclient/tacker/v1_0/vm/device_template.py new file mode 100644 index 00000000..2ac9a714 --- /dev/null +++ b/tackerclient/tacker/v1_0/vm/device_template.py @@ -0,0 +1,94 @@ +# +# Copyright 2013 Intel +# Copyright 2013 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel + +from tackerclient.tacker import v1_0 as tackerV10 + + +_DEVICE_TEMPLATE = "device_template" + + +class ListDeviceTemplate(tackerV10.ListCommand): + """List device template that belong to a given tenant.""" + + resource = _DEVICE_TEMPLATE + + +class ShowDeviceTemplate(tackerV10.ShowCommand): + """show information of a given DeviceTemplate.""" + + resource = _DEVICE_TEMPLATE + + +class CreateDeviceTemplate(tackerV10.CreateCommand): + """create a DeviceTemplate.""" + + resource = _DEVICE_TEMPLATE + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help='Set a name for the devicetemplate') + parser.add_argument( + '--description', + help='Set a description for the devicetemplate') + parser.add_argument( + '--template-service-type', + action='append', + help='Add a servicetype for the devicetemplate') + parser.add_argument( + '--device-driver', + help='Set a device driver name for the devicetemplate') + parser.add_argument( + '--mgmt-driver', + help='Set a manegement driver name for the devicetemplate') + parser.add_argument( + '--attribute', + nargs=2, + action='append', + help='Set a servicetypes for the devicetemplate') + + def args2body(self, parsed_args): + body = { + self.resource: { + 'service_types': [ + {'service_type': service_type} + for service_type in parsed_args.template_service_type], + 'device_driver': parsed_args.device_driver, + 'mgmt_driver': parsed_args.mgmt_driver, + } + } + if parsed_args.attribute: + body[self.resource]['attributes'] = dict(parsed_args.attribute) + tackerV10.update_dict(parsed_args, body[self.resource], + ['tenant_id', 'name', 'description']) + return body + + +class UpdateDeviceTemplate(tackerV10.UpdateCommand): + """Update a given DeviceTemplate.""" + + resource = _DEVICE_TEMPLATE + allow_names = False + + +class DeleteDeviceTemplate(tackerV10.DeleteCommand): + """Delete a given DeviceTemplate.""" + resource = _DEVICE_TEMPLATE diff --git a/tackerclient/tacker/v1_0/vm/service_instance.py b/tackerclient/tacker/v1_0/vm/service_instance.py new file mode 100644 index 00000000..b68ebcf0 --- /dev/null +++ b/tackerclient/tacker/v1_0/vm/service_instance.py @@ -0,0 +1,164 @@ +# +# Copyright 2013 Intel +# Copyright 2013 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel + +from tackerclient.common import exceptions +from tackerclient.openstack.common.gettextutils import _ +from tackerclient.tacker import v1_0 as tackerV10 + + +_SERVICE_INSTANCE = 'service_instance' + + +class ListServiceInstance(tackerV10.ListCommand): + """List service instance that belong to a given tenant.""" + + resource = _SERVICE_INSTANCE + + +class ShowServiceInstance(tackerV10.ShowCommand): + """show information of a given ServiceInstance.""" + + resource = _SERVICE_INSTANCE + + +class CreateServiceInstance(tackerV10.CreateCommand): + """create a ServiceInstance.""" + + resource = _SERVICE_INSTANCE + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + default=None, + help='Set a name for the devicetemplate') + parser.add_argument( + '--service-type-id', + required=True, + help='service type id to create service instance based on') + parser.add_argument( + '--service-table-id', + required=True, + help='service type id to create service instance based on') + parser.add_argument( + '--mgmt-driver', + default=None, + help='Set a manegement driver name for the service instance') + parser.add_argument( + '--service-context', + metavar='', + action='append', + dest='service_context', + default=[], + help='service context to insert service') + parser.add_argument( + '--device', + required=True, + help='Set a device for the service instance to create on') + parser.add_argument( + '--kwargs', + metavar='=', + action='append', + dest='kwargs', + default=[], + help='instance specific argument') + + def args2body(self, parsed_args): + body = { + self.resource: { + 'service_type_id': parsed_args.service_type_id, + 'service_table_id': parsed_args.service_table_id, + 'devices': [parsed_args.device], + } + } + if parsed_args.name is not None: + body[self.resource]['name'] = parsed_args.name + if parsed_args.mgmt_driver is not None: + body[self.resource]['mgmt_driver'] = parsed_args.mgmt_driver + if parsed_args.kwargs: + try: + kwargs = dict(key_value.split('=', 1) + for key_value in parsed_args.kwargs) + except ValueError: + msg = (_('invalid argument for --kwargs %s') % + parsed_args.kwargs) + raise exceptions.TackerCLIError(msg) + if kwargs: + body[self.resource]['kwargs'] = kwargs + if parsed_args.service_context: + try: + service_context = [dict( + (k.replace('-', '_'), v) + for k, v in (key_value.split('=', 1) + for key_value in entry_string.split(','))) + for entry_string in parsed_args.service_context] + except ValueError: + msg = (_('invalid argument for --service-context %s') % + parsed_args.service_context) + raise exceptions.TackerCLIError(msg) + + if service_context: + body[self.resource]['service_context'] = service_context + + tackerV10.update_dict(parsed_args, body[self.resource], ['tenant_id']) + return body + + +class UpdateServiceInstance(tackerV10.UpdateCommand): + """Update a given ServiceInstance.""" + + resource = _SERVICE_INSTANCE + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help='Set a name for the devicetemplate') + parser.add_argument( + '--kwargs', + metavar='=', + action='append', + dest='kwargs', + default=[], + help='instance specific argument') + + def args2body(self, parsed_args): + body = {self.resource: {}} + if parsed_args.name: + body[self.resource]['name'] = parsed_args.name + if parsed_args.kwargs: + try: + kwargs = dict(key_value.split('=', 1) + for key_value in parsed_args.kwargs) + except ValueError: + msg = (_('invalid argument for --kwargs %s') % + parsed_args.kwargs) + raise exceptions.TackerCLIError(msg) + if kwargs: + body[self.resource]['kwargs'] = kwargs + tackerV10.update_dict(parsed_args, body[self.resource], ['tenant_id']) + return body + + +class DeleteServiceInstance(tackerV10.DeleteCommand): + """Delete a given ServiceInstance.""" + + resource = _SERVICE_INSTANCE diff --git a/tackerclient/tests/unit/test_cli20.py b/tackerclient/tests/unit/test_cli10.py similarity index 100% rename from tackerclient/tests/unit/test_cli20.py rename to tackerclient/tests/unit/test_cli10.py diff --git a/tackerclient/tests/unit/test_cli20_extensions.py b/tackerclient/tests/unit/test_cli10_extensions.py similarity index 100% rename from tackerclient/tests/unit/test_cli20_extensions.py rename to tackerclient/tests/unit/test_cli10_extensions.py diff --git a/tackerclient/tests/unit/vm/__init__.py b/tackerclient/tests/unit/vm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/tests/unit/vm/test_cli10_device.py b/tackerclient/tests/unit/vm/test_cli10_device.py new file mode 100644 index 00000000..49998264 --- /dev/null +++ b/tackerclient/tests/unit/vm/test_cli10_device.py @@ -0,0 +1,126 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2014 Intel +# Copyright 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel + +import sys + +from tackerclient.tacker.v1_0.vm import device +from tackerclient.tests.unit import test_cli10 + + +class CLITestV10VmDeviceJSON(test_cli10.CLITestV10Base): + _RESOURCE = 'device' + _RESOURCES = 'devices' + + def setUp(self): + plurals = {'devices': 'device'} + super(CLITestV10VmDeviceJSON, self).setUp(plurals=plurals) + + def test_create_device_all_params(self): + cmd = device.CreateDevice(test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + template_id = 'template_id' + key = 'key' + value = 'value' + network_id = 'network_id' + subnet_id = 'subnet_id' + port_id = 'port_id' + router_id = 'router_id' + role = 'role' + index = 1 + + args = [ + '--device-template-id', template_id, + '--kwargs', '%s=%s' % (key, value), + '--service-context', + ('network-id=%s,subnet-id=%s,port-id=%s,router-id=%s,' + 'role=%s,index=%s' % (network_id, subnet_id, port_id, router_id, + role, index)) + ] + position_names = ['template_id'] + position_values = [template_id] + extra_body = { + 'kwargs': { + key: value + }, + 'service_context': [{ + 'network_id': network_id, + 'subnet_id': subnet_id, + 'port_id': port_id, + 'router_id': router_id, + 'role': role, + 'index': str(index), + }], + } + self._test_create_resource(self._RESOURCE, cmd, None, my_id, + args, position_names, position_values, + extra_body=extra_body) + + def test_create_device_with_mandatory_params(self): + cmd = device.CreateDevice(test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + template_id = 'template_id' + args = [ + '--device-template-id', template_id, + ] + position_names = ['template_id'] + position_values = [template_id] + self._test_create_resource(self._RESOURCE, cmd, None, my_id, + args, position_names, position_values) + + def test_list_devices(self): + cmd = device.ListDevice(test_cli10.MyApp(sys.stdout), None) + self._test_list_resources(self._RESOURCES, cmd, True) + + def test_list_devices_pagenation(self): + cmd = device.ListDevice(test_cli10.MyApp(sys.stdout), None) + self._test_list_resources(self._RESOURCES, cmd, True) + + def test_show_device_id(self): + cmd = device.ShowDevice(test_cli10.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(self._RESOURCE, cmd, self.test_id, args, + ['id']) + + def test_show_device_id_name(self): + cmd = device.ShowDevice(test_cli10.MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(self._RESOURCE, cmd, self.test_id, + args, ['id', 'name']) + + def test_update_device(self): + cmd = device.UpdateDevice(test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + key = 'new-key' + value = 'new-value' + self._test_update_resource(self._RESOURCE, cmd, my_id, + [my_id, '--kwargs', '%s=%s' % (key, value)], + {'kwargs': {key: value}}) + + def test_delete_device(self): + cmd = device.DeleteDevice(test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + args = [my_id] + self._test_delete_resource(self._RESOURCE, cmd, my_id, args) + + +class CLITestV10VmDeviceXML(CLITestV10VmDeviceJSON): + format = 'xml' diff --git a/tackerclient/tests/unit/vm/test_cli10_device_template.py b/tackerclient/tests/unit/vm/test_cli10_device_template.py new file mode 100644 index 00000000..389cd41b --- /dev/null +++ b/tackerclient/tests/unit/vm/test_cli10_device_template.py @@ -0,0 +1,132 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2014 Intel +# Copyright 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel + +import sys + +from tackerclient.tacker.v1_0.vm import device_template +from tackerclient.tests.unit import test_cli10 + + +class CLITestV10VmDeviceTemplateJSON(test_cli10.CLITestV10Base): + _RESOURCE = 'device_template' + _RESOURCES = 'device_templates' + + def setUp(self): + plurals = {'device_templates': 'device_template'} + super(CLITestV10VmDeviceTemplateJSON, self).setUp(plurals=plurals) + + def test_create_device_template_all_params(self): + cmd = device_template.CreateDeviceTemplate( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + name = 'my-name' + description = 'my-description' + service_type = 'MY-SERVICE' + device_driver = 'device-driver' + mgmt_driver = 'mgmt-driver' + attr_key = 'attr-key' + attr_val = 'attr-val' + args = [ + '--name', name, + '--description', description, + '--template-service-type', service_type, + '--device-driver', device_driver, + '--mgmt-driver', mgmt_driver, + '--attribute', attr_key, attr_val, + ] + position_names = ['name', 'description', + 'device_driver', 'mgmt_driver'] + position_values = [name, description, device_driver, mgmt_driver] + extra_body = { + 'service_types': [{'service_type': service_type}], + 'attributes': {attr_key: attr_val}, + } + self._test_create_resource(self._RESOURCE, cmd, None, my_id, + args, position_names, position_values, + extra_body=extra_body) + + def test_create_device_template_with_mandatory_params(self): + cmd = device_template.CreateDeviceTemplate( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + service_type = 'MY-SERVICE' + device_driver = 'device-driver' + mgmt_driver = 'mgmt-driver' + args = [ + '--template-service-type', service_type, + '--device-driver', device_driver, + '--mgmt-driver', mgmt_driver, + ] + position_names = ['device_driver', 'mgmt_driver'] + position_values = [device_driver, mgmt_driver] + extra_body = { + 'service_types': [{'service_type': service_type}], + } + self._test_create_resource(self._RESOURCE, cmd, None, my_id, + args, position_names, position_values, + extra_body=extra_body) + + def test_list_device_templates(self): + cmd = device_template.ListDeviceTemplate(test_cli10.MyApp(sys.stdout), + None) + self._test_list_resources(self._RESOURCES, cmd, True) + + def test_list_device_templates_pagenation(self): + cmd = device_template.ListDeviceTemplate(test_cli10.MyApp(sys.stdout), + None) + self._test_list_resources(self._RESOURCES, cmd, True) + + def test_show_device_template_id(self): + cmd = device_template.ShowDeviceTemplate(test_cli10.MyApp(sys.stdout), + None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(self._RESOURCE, cmd, self.test_id, args, + ['id']) + + def test_show_device_template_id_name(self): + cmd = device_template.ShowDeviceTemplate(test_cli10.MyApp(sys.stdout), + None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(self._RESOURCE, cmd, self.test_id, + args, ['id', 'name']) + + def test_update_device_template(self): + cmd = device_template.UpdateDeviceTemplate( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + name = 'new-name' + description = 'new-description' + self._test_update_resource(self._RESOURCE, cmd, my_id, + [my_id, '--name', name, + '--description', description], + {'name': name, 'description': description}) + + def test_delete_device_tempalte(self): + cmd = device_template.DeleteDeviceTemplate( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + args = [my_id] + self._test_delete_resource(self._RESOURCE, cmd, my_id, args) + + +class CLITestV10VmDeviceTemplateXML(CLITestV10VmDeviceTemplateJSON): + format = 'xml' diff --git a/tackerclient/tests/unit/vm/test_cli10_service_instance.py b/tackerclient/tests/unit/vm/test_cli10_service_instance.py new file mode 100644 index 00000000..67c6ec79 --- /dev/null +++ b/tackerclient/tests/unit/vm/test_cli10_service_instance.py @@ -0,0 +1,155 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2014 Intel +# Copyright 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel + +import sys + +from tackerclient.tacker.v1_0.vm import service_instance +from tackerclient.tests.unit import test_cli10 + + +class CLITestV10VmServiceInstanceJSON(test_cli10.CLITestV10Base): + _RESOURCE = 'service_instance' + _RESOURCES = 'service_instances' + + def setUp(self): + plurals = {'service_instances': 'service_instance'} + super(CLITestV10VmServiceInstanceJSON, self).setUp(plurals=plurals) + + def test_create_service_instance_all_params(self): + cmd = service_instance.CreateServiceInstance( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + name = 'my-name' + service_type_id = 'service-type-id' + service_table_id = 'service-table-id' + mgmt_driver = 'mgmt-driver' + network_id = 'network_id' + subnet_id = 'subnet_id' + port_id = 'port_id' + router_id = 'router_id' + role = 'role' + index = 1 + + device = 'my-device' + + key = 'key' + value = 'value' + + args = [ + '--name', name, + '--service-type-id', service_type_id, + '--service-table-id', service_table_id, + '--mgmt-driver', mgmt_driver, + '--service-context', + ('network-id=%s,subnet-id=%s,port-id=%s,router-id=%s,' + 'role=%s,index=%s' % (network_id, subnet_id, port_id, router_id, + role, index)), + '--device', device, + '--kwargs', '%s=%s' % (key, value), + ] + position_names = ['name', 'service_type_id', 'service_table_id', + 'mgmt_driver'] + position_values = [name, service_type_id, service_table_id, + mgmt_driver] + extra_body = { + 'devices': [device], + 'service_context': [{ + 'network_id': network_id, + 'subnet_id': subnet_id, + 'port_id': port_id, + 'router_id': router_id, + 'role': role, + 'index': str(index), + }], + 'kwargs': { + key: value + }, + } + self._test_create_resource(self._RESOURCE, cmd, None, my_id, + args, position_names, position_values, + extra_body=extra_body) + + def test_create_service_instance_with_mandatory_params(self): + cmd = service_instance.CreateServiceInstance( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + service_type_id = 'service-type-id' + service_table_id = 'service-table-id' + device = 'my-device' + args = [ + '--service-type-id', service_type_id, + '--service-table-id', service_table_id, + '--device', device, + ] + position_names = ['service_type_id', 'service_table_id'] + position_values = [service_type_id, service_table_id] + extra_body = { + 'devices': [device], + } + self._test_create_resource(self._RESOURCE, cmd, None, my_id, + args, position_names, position_values, + extra_body=extra_body) + + def test_list_service_instances(self): + cmd = service_instance.ListServiceInstance( + test_cli10.MyApp(sys.stdout), None) + self._test_list_resources(self._RESOURCES, cmd, True) + + def test_list_service_instances_pagenation(self): + cmd = service_instance.ListServiceInstance( + test_cli10.MyApp(sys.stdout), None) + self._test_list_resources(self._RESOURCES, cmd, True) + + def test_show_service_instance_id(self): + cmd = service_instance.ShowServiceInstance( + test_cli10.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(self._RESOURCE, cmd, self.test_id, args, + ['id']) + + def test_show_service_instance_id_name(self): + cmd = service_instance.ShowServiceInstance( + test_cli10.MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(self._RESOURCE, cmd, self.test_id, + args, ['id', 'name']) + + def test_update_service_instance(self): + cmd = service_instance.UpdateServiceInstance( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + key = 'new-key' + value = 'new-value' + self._test_update_resource(self._RESOURCE, cmd, my_id, + [my_id, '--kwargs', '%s=%s' % (key, value)], + {'kwargs': {key: value}}) + + def test_delete_service_instance(self): + cmd = service_instance.DeleteServiceInstance( + test_cli10.MyApp(sys.stdout), None) + my_id = 'my-id' + args = [my_id] + self._test_delete_resource(self._RESOURCE, cmd, my_id, args) + + +class CLITestV10VmServiceInstanceXML(CLITestV10VmServiceInstanceJSON): + format = 'xml' diff --git a/tackerclient/v1_0/client.py b/tackerclient/v1_0/client.py new file mode 100644 index 00000000..b10a57ce --- /dev/null +++ b/tackerclient/v1_0/client.py @@ -0,0 +1,411 @@ +# Copyright 2012 OpenStack Foundation. +# 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. +# + +import logging +import time +import urllib + +import requests +import six.moves.urllib.parse as urlparse + +from tackerclient import client +from tackerclient.common import _ +from tackerclient.common import constants +from tackerclient.common import exceptions +from tackerclient.common import serializer +from tackerclient.common import utils + + +_logger = logging.getLogger(__name__) + + +def exception_handler_v10(status_code, error_content): + """Exception handler for API v1.0 client + + This routine generates the appropriate + Tacker exception according to the contents of the + response body + + :param status_code: HTTP error status code + :param error_content: deserialized body of error response + """ + error_dict = None + if isinstance(error_content, dict): + error_dict = error_content.get('TackerError') + # Find real error type + bad_tacker_error_flag = False + if error_dict: + # If Tacker key is found, it will definitely contain + # a 'message' and 'type' keys? + try: + error_type = error_dict['type'] + error_message = error_dict['message'] + if error_dict['detail']: + error_message += "\n" + error_dict['detail'] + except Exception: + bad_tacker_error_flag = True + if not bad_tacker_error_flag: + # If corresponding exception is defined, use it. + client_exc = getattr(exceptions, '%sClient' % error_type, None) + # Otherwise look up per status-code client exception + if not client_exc: + client_exc = exceptions.HTTP_EXCEPTION_MAP.get(status_code) + if client_exc: + raise client_exc(message=error_message, + status_code=status_code) + else: + raise exceptions.TackerClientException( + status_code=status_code, message=error_message) + else: + raise exceptions.TackerClientException(status_code=status_code, + message=error_dict) + else: + message = None + if isinstance(error_content, dict): + message = error_content.get('message') + if message: + raise exceptions.TackerClientException(status_code=status_code, + message=message) + + # If we end up here the exception was not a tacker error + msg = "%s-%s" % (status_code, error_content) + raise exceptions.TackerClientException(status_code=status_code, + message=msg) + + +class APIParamsCall(object): + """A Decorator to add support for format and tenant overriding + and filters + """ + def __init__(self, function): + self.function = function + + def __get__(self, instance, owner): + def with_params(*args, **kwargs): + _format = instance.format + if 'format' in kwargs: + instance.format = kwargs['format'] + ret = self.function(instance, *args, **kwargs) + instance.format = _format + return ret + return with_params + + +class Client(object): + """Client for the OpenStack Tacker v1.0 API. + + :param string username: Username for authentication. (optional) + :param string user_id: User ID for authentication. (optional) + :param string password: Password for authentication. (optional) + :param string token: Token for authentication. (optional) + :param string tenant_name: Tenant name. (optional) + :param string tenant_id: Tenant id. (optional) + :param string auth_url: Keystone service endpoint for authorization. + :param string service_type: Network service type to pull from the + keystone catalog (e.g. 'network') (optional) + :param string endpoint_type: Network service endpoint type to pull from the + keystone catalog (e.g. 'publicURL', + 'internalURL', or 'adminURL') (optional) + :param string region_name: Name of a region to select when choosing an + endpoint from the service catalog. + :param string endpoint_url: A user-supplied endpoint URL for the tacker + service. Lazy-authentication is possible for API + service calls if endpoint is set at + instantiation.(optional) + :param integer timeout: Allows customization of the timeout for client + http requests. (optional) + :param bool insecure: SSL certificate validation. (optional) + :param string ca_cert: SSL CA bundle file to use. (optional) + + Example:: + + from tackerclient.v1_0 import client + tacker = client.Client(username=USER, + password=PASS, + tenant_name=TENANT_NAME, + auth_url=KEYSTONE_URL) + + nets = tacker.list_networks() + ... + + """ + + extensions_path = "/extensions" + extension_path = "/extensions/%s" + + device_templates_path = '/device-templates' + device_template_path = '/device-templates/%s' + service_instances_path = '/service-instances' + service_instance_path = '/service-instances/%s' + devices_path = '/devices' + device_path = '/devices/%s' + + # API has no way to report plurals, so we have to hard code them + EXTED_PLURALS = {} + # 8192 Is the default max URI len for eventlet.wsgi.server + MAX_URI_LEN = 8192 + + def get_attr_metadata(self): + if self.format == 'json': + return {} + old_request_format = self.format + self.format = 'json' + exts = self.list_extensions()['extensions'] + self.format = old_request_format + ns = dict([(ext['alias'], ext['namespace']) for ext in exts]) + self.EXTED_PLURALS.update(constants.PLURALS) + return {'plurals': self.EXTED_PLURALS, + 'xmlns': constants.XML_NS_V10, + constants.EXT_NS: ns} + + @APIParamsCall + def list_extensions(self, **_params): + """Fetch a list of all exts on server side.""" + return self.get(self.extensions_path, params=_params) + + @APIParamsCall + def show_extension(self, ext_alias, **_params): + """Fetch a list of all exts on server side.""" + return self.get(self.extension_path % ext_alias, params=_params) + + def list_device_templates(self, retrieve_all=True, **_params): + return self.list('device_templates', self.device_templates_path, + retrieve_all, **_params) + + @APIParamsCall + def show_device_template(self, device_template, **_params): + return self.get(self.device_template_path % device_template, + params=_params) + + @APIParamsCall + def update_device_template(self, device_template, body=None): + return self.put(self.device_template_path % device_template, body=body) + + @APIParamsCall + def create_device_template(self, body=None): + return self.post(self.device_templates_path, body=body) + + @APIParamsCall + def delete_device_template(self, device_template): + return self.delete(self.device_template_path % device_template) + + @APIParamsCall + def list_service_instances(self, retrieve_all=True, **_params): + return self.list('service_instances', self.service_instances_path, + retrieve_all, **_params) + + @APIParamsCall + def show_service_instance(self, service_instance, **_params): + return self.get(self.service_instance_path % service_instance, + params=_params) + + @APIParamsCall + def update_service_instance(self, service_instance, body=None): + return self.put(self.service_instance_path % service_instance, + body=body) + + @APIParamsCall + def create_service_instance(self, body=None): + return self.post(self.service_instances_path, body=body) + + @APIParamsCall + def delete_service_instance(self, service_instance): + return self.delete(self.service_instance_path % service_instance) + + @APIParamsCall + def list_devices(self, retrieve_all=True, **_params): + return self.list('devices', self.devices_path, retrieve_all, **_params) + + @APIParamsCall + def show_device(self, device, **_params): + return self.get(self.device_path % device, params=_params) + + @APIParamsCall + def update_device(self, device, body=None): + return self.put(self.device_path % device, body=body) + + @APIParamsCall + def create_device(self, body=None): + return self.post(self.devices_path, body=body) + + @APIParamsCall + def delete_device(self, device): + return self.delete(self.device_path % device) + + def __init__(self, **kwargs): + """Initialize a new client for the Tacker v1.0 API.""" + super(Client, self).__init__() + self.httpclient = client.HTTPClient(**kwargs) + self.version = '1.0' + self.format = 'json' + self.action_prefix = "/v%s" % (self.version) + self.retries = 0 + self.retry_interval = 1 + + def _handle_fault_response(self, status_code, response_body): + # Create exception with HTTP status code and message + _logger.debug(_("Error message: %s"), response_body) + # Add deserialized error message to exception arguments + try: + des_error_body = self.deserialize(response_body, status_code) + except Exception: + # If unable to deserialized body it is probably not a + # Tacker error + des_error_body = {'message': response_body} + # Raise the appropriate exception + exception_handler_v10(status_code, des_error_body) + + def _check_uri_length(self, action): + uri_len = len(self.httpclient.endpoint_url) + len(action) + if uri_len > self.MAX_URI_LEN: + raise exceptions.RequestURITooLong( + excess=uri_len - self.MAX_URI_LEN) + + def do_request(self, method, action, body=None, headers=None, params=None): + # Add format and tenant_id + action += ".%s" % self.format + action = self.action_prefix + action + if type(params) is dict and params: + params = utils.safe_encode_dict(params) + action += '?' + urllib.urlencode(params, doseq=1) + # Ensure client always has correct uri - do not guesstimate anything + self.httpclient.authenticate_and_fetch_endpoint_url() + self._check_uri_length(action) + + if body: + body = self.serialize(body) + self.httpclient.content_type = self.content_type() + resp, replybody = self.httpclient.do_request(action, method, body=body) + status_code = self.get_status_code(resp) + if status_code in (requests.codes.ok, + requests.codes.created, + requests.codes.accepted, + requests.codes.no_content): + return self.deserialize(replybody, status_code) + else: + if not replybody: + replybody = resp.reason + self._handle_fault_response(status_code, replybody) + + def get_auth_info(self): + return self.httpclient.get_auth_info() + + def get_status_code(self, response): + """Returns the integer status code from the response. + + Either a Webob.Response (used in testing) or requests.Response + is returned. + """ + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status_code + + def serialize(self, data): + """Serializes a dictionary into either xml or json. + + A dictionary with a single key can be passed and + it can contain any structure. + """ + if data is None: + return None + elif type(data) is dict: + return serializer.Serializer( + self.get_attr_metadata()).serialize(data, self.content_type()) + else: + raise Exception(_("Unable to serialize object of type = '%s'") % + type(data)) + + def deserialize(self, data, status_code): + """Deserializes an xml or json string into a dictionary.""" + if status_code == 204: + return data + return serializer.Serializer(self.get_attr_metadata()).deserialize( + data, self.content_type())['body'] + + def content_type(self, _format=None): + """Returns the mime-type for either 'xml' or 'json'. + + Defaults to the currently set format. + """ + _format = _format or self.format + return "application/%s" % (_format) + + def retry_request(self, method, action, body=None, + headers=None, params=None): + """Call do_request with the default retry configuration. + + Only idempotent requests should retry failed connection attempts. + :raises: ConnectionFailed if the maximum # of retries is exceeded + """ + max_attempts = self.retries + 1 + for i in range(max_attempts): + try: + return self.do_request(method, action, body=body, + headers=headers, params=params) + except exceptions.ConnectionFailed: + # Exception has already been logged by do_request() + if i < self.retries: + _logger.debug(_('Retrying connection to Tacker service')) + time.sleep(self.retry_interval) + + raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached")) + + def delete(self, action, body=None, headers=None, params=None): + return self.retry_request("DELETE", action, body=body, + headers=headers, params=params) + + def get(self, action, body=None, headers=None, params=None): + return self.retry_request("GET", action, body=body, + headers=headers, params=params) + + def post(self, action, body=None, headers=None, params=None): + # Do not retry POST requests to avoid the orphan objects problem. + return self.do_request("POST", action, body=body, + headers=headers, params=params) + + def put(self, action, body=None, headers=None, params=None): + return self.retry_request("PUT", action, body=body, + headers=headers, params=params) + + def list(self, collection, path, retrieve_all=True, **params): + if retrieve_all: + res = [] + for r in self._pagination(collection, path, **params): + res.extend(r[collection]) + return {collection: res} + else: + return self._pagination(collection, path, **params) + + def _pagination(self, collection, path, **params): + if params.get('page_reverse', False): + linkrel = 'previous' + else: + linkrel = 'next' + next = True + while next: + res = self.get(path, params=params) + yield res + next = False + try: + for link in res['%s_links' % collection]: + if link['rel'] == linkrel: + query_str = urlparse.urlparse(link['href']).query + params = urlparse.parse_qs(query_str) + next = True + break + except KeyError: + break