Merge "Configure pagination"
diff --git a/.gitignore b/.gitignore
index 1bfce6e..aa8e42a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
+.kitchen
tests/build/
*.swp
*.pyc
-.ropeproject
\ No newline at end of file
+.ropeproject
diff --git a/Makefile b/Makefile
index fc83783..1043fbe 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,38 @@
DESTDIR=/
SALTENVDIR=/usr/share/salt-formulas/env
RECLASSDIR=/usr/share/salt-formulas/reclass
-FORMULANAME=$(shell grep name: metadata.yml|head -1|cut -d : -f 2|grep -Eo '[a-z0-9\-]*')
+FORMULANAME=$(shell grep name: metadata.yml|head -1|cut -d : -f 2|grep -Eo '[a-z0-9\-\_]*')
+VERSION=$(shell grep version: metadata.yml|head -1|cut -d : -f 2|grep -Eo '[a-z0-9\.\-\_]*')
+VERSION_MAJOR := $(shell echo $(VERSION)|cut -d . -f 1-2)
+VERSION_MINOR := $(shell echo $(VERSION)|cut -d . -f 3)
+
+NEW_MAJOR_VERSION ?= $(shell date +%Y.%m|sed 's,\.0,\.,g')
+NEW_MINOR_VERSION ?= $(shell /bin/bash -c 'echo $$[ $(VERSION_MINOR) + 1 ]')
+
+MAKE_PID := $(shell echo $$PPID)
+JOB_FLAG := $(filter -j%, $(subst -j ,-j,$(shell ps T | grep "^\s*$(MAKE_PID).*$(MAKE)")))
+
+ifneq ($(subst -j,,$(JOB_FLAG)),)
+JOBS := $(subst -j,,$(JOB_FLAG))
+else
+JOBS := 1
+endif
+
+KITCHEN_LOCAL_YAML?=.kitchen.yml
+KITCHEN_OPTS?="--concurrency=$(JOBS)"
+KITCHEN_OPTS_CREATE?=""
+KITCHEN_OPTS_CONVERGE?=""
+KITCHEN_OPTS_VERIFY?=""
+KITCHEN_OPTS_TEST?=""
all:
@echo "make install - Install into DESTDIR"
@echo "make test - Run tests"
+ @echo "make kitchen - Run Kitchen CI tests (create, converge, verify)"
@echo "make clean - Cleanup after tests run"
+ @echo "make release-major - Generate new major release"
+ @echo "make release-minor - Generate new minor release"
+ @echo "make changelog - Show changes since last release"
install:
# Formula
@@ -14,6 +40,7 @@
cp -a $(FORMULANAME) $(DESTDIR)/$(SALTENVDIR)/
[ ! -d _modules ] || cp -a _modules $(DESTDIR)/$(SALTENVDIR)/
[ ! -d _states ] || cp -a _states $(DESTDIR)/$(SALTENVDIR)/ || true
+ [ ! -d _grains ] || cp -a _grains $(DESTDIR)/$(SALTENVDIR)/ || true
# Metadata
[ -d $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME) ] || mkdir -p $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME)
cp -a metadata/service/* $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME)
@@ -21,6 +48,71 @@
test:
[ ! -d tests ] || (cd tests; ./run_tests.sh)
+release-major: check-changes
+ @echo "Current version is $(VERSION), new version is $(NEW_MAJOR_VERSION)"
+ @[ $(VERSION_MAJOR) != $(NEW_MAJOR_VERSION) ] || (echo "Major version $(NEW_MAJOR_VERSION) already released, nothing to do. Do you want release-minor?" && exit 1)
+ echo "$(NEW_MAJOR_VERSION)" > VERSION
+ sed -i 's,version: .*,version: "$(NEW_MAJOR_VERSION)",g' metadata.yml
+ [ ! -f debian/changelog ] || dch -v $(NEW_MAJOR_VERSION) -m --force-distribution -D `dpkg-parsechangelog -S Distribution` "New version"
+ make genchangelog-$(NEW_MAJOR_VERSION)
+ (git add -u; git commit -m "Version $(NEW_MAJOR_VERSION)")
+ git tag -s -m $(NEW_MAJOR_VERSION) $(NEW_MAJOR_VERSION)
+
+release-minor: check-changes
+ @echo "Current version is $(VERSION), new version is $(VERSION_MAJOR).$(NEW_MINOR_VERSION)"
+ echo "$(VERSION_MAJOR).$(NEW_MINOR_VERSION)" > VERSION
+ sed -i 's,version: .*,version: "$(VERSION_MAJOR).$(NEW_MINOR_VERSION)",g' metadata.yml
+ [ ! -f debian/changelog ] || dch -v $(VERSION_MAJOR).$(NEW_MINOR_VERSION) -m --force-distribution -D `dpkg-parsechangelog -S Distribution` "New version"
+ make genchangelog-$(VERSION_MAJOR).$(NEW_MINOR_VERSION)
+ (git add -u; git commit -m "Version $(VERSION_MAJOR).$(NEW_MINOR_VERSION)")
+ git tag -s -m $(NEW_MAJOR_VERSION) $(VERSION_MAJOR).$(NEW_MINOR_VERSION)
+
+check-changes:
+ @git log --pretty=oneline --decorate $(VERSION)..HEAD | grep -Eqc '.*' || (echo "No new changes since version $(VERSION)"; exit 1)
+
+changelog:
+ git log --pretty=short --invert-grep --grep="Merge pull request" --decorate $(VERSION)..HEAD
+
+genchangelog: genchangelog-$(VERSION_MAJOR).$(NEW_MINOR_VERSION)
+
+genchangelog-%:
+ $(eval NEW_VERSION := $(patsubst genchangelog-%,%,$@))
+ (echo "=========\nChangelog\n=========\n"; \
+ (echo $(NEW_VERSION);git tag) | sort -r | grep -E '^[0-9\.]+' | while read i; do \
+ cur=$$i; \
+ test $$i = $(NEW_VERSION) && i=HEAD; \
+ prev=`(echo $(NEW_VERSION);git tag)|sort|grep -E '^[0-9\.]+'|grep -B1 "$$cur\$$"|head -1`; \
+ echo "Version $$cur\n=============================\n"; \
+ git log --pretty=short --invert-grep --grep="Merge pull request" --decorate $$prev..$$i; \
+ echo; \
+ done) > CHANGELOG.rst
+
+kitchen-check:
+ @[ -e $(KITCHEN_LOCAL_YAML) ] || (echo "Kitchen tests not available, there's no $(KITCHEN_LOCAL_YAML)." && exit 1)
+
+kitchen: kitchen-check kitchen-create kitchen-converge kitchen-verify kitchen-list
+
+kitchen-create: kitchen-check
+ kitchen create ${KITCHEN_OPTS} ${KITCHEN_OPTS_CREATE}
+ [ "$(shell echo $(KITCHEN_LOCAL_YAML)|grep -Eo docker)" = "docker" ] || sleep 120
+
+kitchen-converge: kitchen-check
+ kitchen converge ${KITCHEN_OPTS} ${KITCHEN_OPTS_CONVERGE} &&\
+ kitchen converge ${KITCHEN_OPTS} ${KITCHEN_OPTS_CONVERGE}
+
+kitchen-verify: kitchen-check
+ [ ! -d tests/integration ] || kitchen verify -t tests/integration ${KITCHEN_OPTS} ${KITCHEN_OPTS_VERIFY}
+ [ -d tests/integration ] || kitchen verify ${KITCHEN_OPTS} ${KITCHEN_OPTS_VERIFY}
+
+kitchen-test: kitchen-check
+ [ ! -d tests/integration ] || kitchen test -t tests/integration ${KITCHEN_OPTS} ${KITCHEN_OPTS_TEST}
+ [ -d tests/integration ] || kitchen test ${KITCHEN_OPTS} ${KITCHEN_OPTS_TEST}
+
+kitchen-list: kitchen-check
+ kitchen list
+
clean:
+ [ ! -x "$(shell which kitchen)" ] || kitchen destroy
+ [ ! -d .kitchen ] || rm -rf .kitchen
[ ! -d tests/build ] || rm -rf tests/build
[ ! -d build ] || rm -rf build
diff --git a/README.rst b/README.rst
index 4d4d1ca..bcce38e 100644
--- a/README.rst
+++ b/README.rst
@@ -630,3 +630,36 @@
Developers should also join the discussion on the IRC list, at:
https://wiki.openstack.org/wiki/Meetings/openstack-salt
+
+Documentation and Bugs
+======================
+
+To learn how to install and update salt-formulas, consult the documentation
+available online at:
+
+ http://salt-formulas.readthedocs.io/
+
+In the unfortunate event that bugs are discovered, they should be reported to
+the appropriate issue tracker. Use Github issue tracker for specific salt
+formula:
+
+ https://github.com/salt-formulas/salt-formula-neutron/issues
+
+For feature requests, bug reports or blueprints affecting entire ecosystem,
+use Launchpad salt-formulas project:
+
+ https://launchpad.net/salt-formulas
+
+You can also join salt-formulas-users team and subscribe to mailing list:
+
+ https://launchpad.net/~salt-formulas-users
+
+Developers wishing to work on the salt-formulas projects should always base
+their work on master branch and submit pull request against specific formula.
+
+ https://github.com/salt-formulas/salt-formula-neutron
+
+Any questions or feedback is always welcome so feel free to join our IRC
+channel:
+
+ #salt-formulas @ irc.freenode.net
diff --git a/neutron/_modules/neutronng.py b/neutron/_modules/neutronng.py
new file mode 100644
index 0000000..ba7060d
--- /dev/null
+++ b/neutron/_modules/neutronng.py
@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+
+import logging
+from functools import wraps
+LOG = logging.getLogger(__name__)
+# Import third party libs
+HAS_NEUTRON = False
+try:
+ from neutronclient.v2_0 import client
+ HAS_NEUTRON = True
+except ImportError:
+ pass
+
+__opts__ = {}
+
+
+def __virtual__():
+ '''
+ Only load this module if neutron
+ is installed on this minion.
+ '''
+ if HAS_NEUTRON:
+ return 'neutronng'
+ return False
+
+
+def _autheticate(func_name):
+ '''
+ Authenticate requests with the salt keystone module and format return data
+ '''
+ @wraps(func_name)
+ def decorator_method(*args, **kwargs):
+ '''
+ Authenticate request and format return data
+ '''
+ connection_args = {'profile': kwargs.get('profile', None)}
+ nkwargs = {}
+ for kwarg in kwargs:
+ if 'connection_' in kwarg:
+ connection_args.update({kwarg: kwargs[kwarg]})
+ elif '__' not in kwarg:
+ nkwargs.update({kwarg: kwargs[kwarg]})
+ kstone = __salt__['keystone.auth'](**connection_args)
+ token = kstone.auth_token
+ endpoint = kstone.service_catalog.url_for(
+ service_type='network',
+ endpoint_type='publicURL')
+ neutron_interface = client.Client(
+ endpoint_url=endpoint, token=token)
+ return_data = func_name(neutron_interface, *args, **nkwargs)
+ if isinstance(return_data, list):
+ # format list as a dict for rendering
+ return {data.get('name', None) or data['id']: data
+ for data in return_data}
+ return return_data
+ return decorator_method
+
+
+@_autheticate
+def list_floatingips(neutron_interface, **kwargs):
+ '''
+ list all floatingips
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.list_floatingips
+ '''
+ return neutron_interface.list_floatingips(**kwargs)['floatingips']
+
+
+@_autheticate
+def list_security_groups(neutron_interface, **kwargs):
+ '''
+ list all security_groups
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.list_security_groups
+ '''
+ return neutron_interface.list_security_groups(**kwargs)['security_groups']
+
+
+@_autheticate
+def list_subnets(neutron_interface, **kwargs):
+ '''
+ list all subnets
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.list_subnets
+ '''
+ return neutron_interface.list_subnets(**kwargs)['subnets']
+
+
+@_autheticate
+def list_networks(neutron_interface, **kwargs):
+ '''
+ list all networks
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.list_networks
+ '''
+ return neutron_interface.list_networks(**kwargs)['networks']
+
+
+@_autheticate
+def list_ports(neutron_interface, **kwargs):
+ '''
+ list all ports
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.list_ports
+ '''
+ return neutron_interface.list_ports(**kwargs)['ports']
+
+
+@_autheticate
+def list_routers(neutron_interface, **kwargs):
+ '''
+ list all routers
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.list_routers
+ '''
+ return neutron_interface.list_routers(**kwargs)['routers']
+
+@_autheticate
+def update_floatingip(neutron_interface, fip, port_id=None):
+ '''
+ update floating IP. Should be used to associate and disassociate
+ floating IP with instance
+ CLI Example:
+ .. code-block:: bash
+ to associate with an instance's port
+ salt '*' neutron.update_floatingip openstack-floatingip-id port-id
+ to disassociate from an instance's port
+ salt '*' neutron.update_floatingip openstack-floatingip-id
+ '''
+ neutron_interface.update_floatingip(fip, {"floatingip":
+ {"port_id": port_id}})
+
+
+@_autheticate
+def update_subnet(neutron_interface, subnet_id, **subnet_params):
+ '''
+ update given subnet
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.update_subnet openstack-subnet-id name='new_name'
+ '''
+ neutron_interface.update_subnet(subnet_id, {'subnet': subnet_params})
+
+
+@_autheticate
+def update_network(neutron_interface, network_id, **net_params):
+ '''
+ Update give network
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.update_network openstack-net-id admin_state_up=false
+ '''
+ network_params = {}
+ for param in net_params:
+ if 'provider_' in param or 'router_' in param:
+ network_params[param.replace('_', ':', 1)] = net_params[param]
+ else:
+ network_params[param] = net_params[param]
+ LOG.info('ATTRIBUTES ' + str(network_params))
+ neutron_interface.update_network(network_id, {'network': network_params})
+
+
+@_autheticate
+def update_router(neutron_interface, router_id, **router_params):
+ '''
+ update given router
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.update_router openstack-router-id name='new_name'
+ external_gateway='openstack-network-id' administrative_state=true
+ '''
+ neutron_interface.update_router(router_id, {'router': router_params})
+
+
+@_autheticate
+def router_gateway_set(neutron_interface, router_id, external_gateway):
+ '''
+ Set external gateway for a router
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.update_router openstack-router-id openstack-network-id
+ '''
+ neutron_interface.update_router(
+ router_id, {'router': {'external_gateway_info':
+ {'network_id': external_gateway}}})
+
+
+@_autheticate
+def router_gateway_clear(neutron_interface, router_id):
+ '''
+ Clear external gateway for a router
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.update_router openstack-router-id
+ '''
+ neutron_interface.update_router(
+ router_id, {'router': {'external_gateway_info': None}})
+
+
+@_autheticate
+def create_router(neutron_interface, **router_params):
+ '''
+ Create OpenStack Neutron router
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.create_router name=R1
+ '''
+ response = neutron_interface.create_router({'router': router_params})
+ if 'router' in response and 'id' in response['router']:
+ return response['router']['id']
+
+
+@_autheticate
+def router_add_interface(neutron_interface, router_id, subnet_id):
+ '''
+ Attach router to a subnet
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.router_add_interface openstack-router-id subnet-id
+ '''
+ neutron_interface.add_interface_router(router_id, {'subnet_id': subnet_id})
+
+
+@_autheticate
+def router_rem_interface(neutron_interface, router_id, subnet_id):
+ '''
+ Dettach router from a subnet
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.router_rem_interface openstack-router-id subnet-id
+ '''
+ neutron_interface.remove_interface_router(
+ router_id, {'subnet_id': subnet_id})
+
+
+@_autheticate
+def create_security_group(neutron_interface, **sg_params):
+ '''
+ Create a new security group
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.create_security_group name='new_rule'
+ description='test rule'
+ '''
+ response = neutron_interface.create_security_group(
+ {'security_group': sg_params})
+ if 'security_group' in response and 'id' in response['security_group']:
+ return response['security_group']['id']
+
+
+@_autheticate
+def create_security_group_rule(neutron_interface, **rule_params):
+ '''
+ Create a rule entry for a security group
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.create_security_group_rule
+ '''
+ neutron_interface.create_security_group_rule(
+ {'security_group_rule': rule_params})
+
+
+@_autheticate
+def create_floatingip(neutron_interface, **floatingip_params):
+ '''
+ Create a new floating IP
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.create_floatingip floating_network_id=ext-net-id
+ '''
+ response = neutron_interface.create_floatingip(
+ {'floatingip': floatingip_params})
+ if 'floatingip' in response and 'id' in response['floatingip']:
+ return response['floatingip']['id']
+
+
+@_autheticate
+def create_subnet(neutron_interface, **subnet_params):
+ '''
+ Create a new subnet in OpenStack
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.create_subnet name='subnet name'
+ network_id='openstack-network-id' cidr='192.168.10.0/24' \\
+ gateway_ip='192.168.10.1' ip_version='4' enable_dhcp=false \\
+ start_ip='192.168.10.10' end_ip='192.168.10.20'
+ '''
+ if 'start_ip' in subnet_params:
+ subnet_params.update(
+ {'allocation_pools': [{'start': subnet_params.pop('start_ip'),
+ 'end': subnet_params.pop('end_ip', None)}]})
+ response = neutron_interface.create_subnet({'subnet': subnet_params})
+ if 'subnet' in response and 'id' in response['subnet']:
+ return response['subnet']['id']
+
+
+@_autheticate
+def create_network(neutron_interface, **net_params):
+ '''
+ Create a new network segment in OpenStack
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.create_network name=External
+ provider_network_type=flat provider_physical_network=ext
+ '''
+ network_params = {}
+ for param in net_params:
+ if 'provider_' in param or 'router_' in param:
+ network_params[param.replace('_', ':', 1)] = net_params[param]
+ else:
+ network_params[param] = net_params[param]
+ response = neutron_interface.create_network({'network': network_params})
+ if 'network' in response and 'id' in response['network']:
+ return response['network']['id']
+
+
+@_autheticate
+def create_port(neutron_interface, **port_params):
+ '''
+ Create a new port in OpenStack
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.create_port network_id='openstack-network-id'
+ '''
+ response = neutron_interface.create_port({'port': port_params})
+ if 'port' in response and 'id' in response['port']:
+ return response['port']['id']
+
+
+@_autheticate
+def update_port(neutron_interface, port_id, **port_params):
+ '''
+ Create a new port in OpenStack
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.update_port name='new_port_name'
+ '''
+ neutron_interface.update_port(port_id, {'port': port_params})
+
+
+@_autheticate
+def delete_floatingip(neutron_interface, floating_ip_id):
+ '''
+ delete a floating IP
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.delete_floatingip openstack-floating-ip-id
+ '''
+ neutron_interface.delete_floatingip(floating_ip_id)
+
+
+@_autheticate
+def delete_security_group(neutron_interface, sg_id):
+ '''
+ delete a security group
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.delete_security_group openstack-security-group-id
+ '''
+ neutron_interface.delete_security_group(sg_id)
+
+
+@_autheticate
+def delete_security_group_rule(neutron_interface, rule):
+ '''
+ delete a security group rule. pass all rule params that match the rule
+ to be deleted
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.delete_security_group_rule direction='ingress'
+ ethertype='ipv4' security_group_id='openstack-security-group-id'
+ port_range_min=100 port_range_max=4096 protocol='tcp'
+ remote_group_id='default'
+ '''
+ sg_rules = neutron_interface.list_security_group_rules(
+ security_group_id=rule['security_group_id'])
+ for sg_rule in sg_rules['security_group_rules']:
+ sgr_id = sg_rule.pop('id')
+ if sg_rule == rule:
+ neutron_interface.delete_security_group_rule(sgr_id)
+
+
+@_autheticate
+def delete_subnet(neutron_interface, subnet_id):
+ '''
+ delete given subnet
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.delete_subnet openstack-subnet-id
+ '''
+ neutron_interface.delete_subnet(subnet_id)
+
+
+@_autheticate
+def delete_network(neutron_interface, network_id):
+ '''
+ delete given network
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.delete_network openstack-network-id
+ '''
+ neutron_interface.delete_network(network_id)
+
+
+@_autheticate
+def delete_router(neutron_interface, router_id):
+ '''
+ delete given router
+ CLI Example:
+ .. code-block:: bash
+ salt '*' neutron.delete_router openstack-router-id
+ '''
+ neutron_interface.delete_router(router_id)
\ No newline at end of file
diff --git a/neutron/_states/neutronng.py b/neutron/_states/neutronng.py
new file mode 100644
index 0000000..3a7483b
--- /dev/null
+++ b/neutron/_states/neutronng.py
@@ -0,0 +1,528 @@
+# -*- coding: utf-8 -*-
+'''
+Management of Neutron resources
+===============================
+:depends: - neutronclient Python module
+:configuration: See :py:mod:`salt.modules.neutron` for setup instructions.
+.. code-block:: yaml
+ neutron network present:
+ neutron.network_present:
+ - name: Netone
+ - provider_physical_network: PHysnet1
+ - provider_network_type: vlan
+'''
+import logging
+from functools import wraps
+LOG = logging.getLogger(__name__)
+
+
+def __virtual__():
+ '''
+ Only load if neutron module is present in __salt__
+ '''
+ return 'neutronng' if 'neutron.list_networks' in __salt__ else False
+
+
+def _test_call(method):
+ (resource, functionality) = method.func_name.split('_')
+ if functionality == 'present':
+ functionality = 'updated'
+ else:
+ functionality = 'removed'
+
+ @wraps(method)
+ def check_for_testing(name, *args, **kwargs):
+ if __opts__.get('test', None):
+ return _no_change(name, resource, test=functionality)
+ return method(name, *args, **kwargs)
+ return check_for_testing
+
+
+def _neutron_module_call(method, *args, **kwargs):
+ return __salt__['neutronng.{0}'.format(method)](*args, **kwargs)
+
+
+def _auth(profile=None):
+ '''
+ Set up neutron credentials
+ '''
+ if profile:
+ credentials = __salt__['config.option'](profile)
+ user = credentials['keystone.user']
+ password = credentials['keystone.password']
+ tenant = credentials['keystone.tenant']
+ auth_url = credentials['keystone.auth_url']
+
+ kwargs = {
+ 'connection_user': user,
+ 'connection_password': password,
+ 'connection_tenant': tenant,
+ 'connection_auth_url': auth_url
+ }
+
+ return kwargs
+
+@_test_call
+def network_present(name=None,
+ provider_network_type=None,
+ provider_physical_network=None,
+ router_external=None,
+ admin_state_up=None,
+ shared=None,
+ provider_segmentation_id=None,
+ profile=None):
+ '''
+ Ensure that the neutron network is present with the specified properties.
+ name
+ The name of the network to manage
+ '''
+ #for p in connection_args: print p
+ connection_args = _auth(profile)
+ print connection_args
+ for p in connection_args: print p
+ print("neco")
+ print provider_network_type
+ print name
+ print provider_physical_network
+ print router_external
+ print admin_state_up
+ print shared
+ print provider_segmentation_id
+ existing_network = _neutron_module_call(
+ 'list_networks', name=name, **connection_args)
+ network_arguments = _get_non_null_args(
+ name=name,
+ provider_network_type=provider_network_type,
+ provider_physical_network=provider_physical_network,
+ router_external=router_external,
+ admin_state_up=admin_state_up,
+ shared=shared,
+ provider_segmentation_id=provider_segmentation_id)
+ #for p in network_arguments: print p
+ if not existing_network:
+ network_arguments.update(connection_args)
+ _neutron_module_call('create_network', **network_arguments)
+ existing_network = _neutron_module_call(
+ 'list_networks', name=name, **connection_args)
+ if existing_network:
+ return _created(name, 'network', existing_network[name])
+ return _update_failed(name, 'network')
+ # map internal representation to display format
+ #for p in connection_args: print p
+ LOG.info('CONNECTION STRINGS' + str(connection_args))
+ LOG.info('existing ' + str(existing_network))
+ LOG.info('new ' + str(network_arguments))
+ existing_network = dict((key.replace(':', '_', 1), value)
+ for key, value in
+ existing_network[name].iteritems())
+ # generate differential
+ diff = dict((key, value) for key, value in network_arguments.iteritems()
+ if existing_network.get(key, None) != value)
+ if diff:
+ # update the changes
+ network_arguments = diff.copy()
+ network_arguments.update(connection_args)
+ try:
+ LOG.debug('updating network {0} with changes {1}'.format(
+ name, str(diff)))
+ _neutron_module_call('update_network',
+ existing_network['id'],
+ **network_arguments)
+ changes_dict = _created(name, 'network', diff)
+ changes_dict['comment'] = '{1} {0} updated'.format(name, 'network')
+ return changes_dict
+ except:
+ LOG.exception('Could not update network {0}'.format(name))
+ return _update_failed(name, 'network')
+ return _no_change(name, 'network')
+
+
+@_test_call
+def network_absent(name, profile=None):
+ connection_args = _auth(profile)
+ existing_network = _neutron_module_call(
+ 'list_networks', name=name, **connection_args)
+ if existing_network:
+ _neutron_module_call(
+ 'delete_network', existing_network[name]['id'], **connection_args)
+ if _neutron_module_call('list_networks', name=name, **connection_args):
+ return _delete_failed(name, 'network')
+ return _deleted(name, 'network', existing_network[name])
+ return _absent(name, 'network')
+
+
+@_test_call
+def subnet_present(name=None,
+ network=None,
+ cidr=None,
+ ip_version=4,
+ enable_dhcp=True,
+ allocation_pools=None,
+ gateway_ip=None,
+ dns_nameservers=None,
+ host_routes=None,
+ profile=None):
+ '''
+ Ensure that the neutron subnet is present with the specified properties.
+ name
+ The name of the subnet to manage
+ '''
+ connection_args = _auth(profile)
+
+ existing_subnet = _neutron_module_call(
+ 'list_subnets', name=name, **connection_args)
+ subnet_arguments = _get_non_null_args(
+ name=name,
+ network=network,
+ cidr=cidr,
+ ip_version=ip_version,
+ enable_dhcp=enable_dhcp,
+ allocation_pools=allocation_pools,
+ gateway_ip=gateway_ip,
+ dns_nameservers=dns_nameservers,
+ host_routes=host_routes)
+ # replace network with network_id
+ if 'network' in subnet_arguments:
+ network = subnet_arguments.pop('network', None)
+ existing_network = _neutron_module_call(
+ 'list_networks', name=network, **connection_args)
+ if existing_network:
+ subnet_arguments['network_id'] = existing_network[network]['id']
+ if not existing_subnet:
+ subnet_arguments.update(connection_args)
+ _neutron_module_call('create_subnet', **subnet_arguments)
+ existing_subnet = _neutron_module_call(
+ 'list_subnets', name=name, **connection_args)
+ if existing_subnet:
+ return _created(name, 'subnet', existing_subnet[name])
+ return _update_failed(name, 'subnet')
+ # change from internal representation
+ existing_subnet = existing_subnet[name]
+ # create differential
+ LOG.error('existing ' + str(existing_subnet))
+ LOG.error('new ' + str(subnet_arguments))
+ diff = dict((key, value) for key, value in subnet_arguments.iteritems()
+ if existing_subnet.get(key, None) != value)
+ if diff:
+ # update the changes
+ subnet_arguments = diff.copy()
+ subnet_arguments.update(connection_args)
+ try:
+ LOG.debug('updating subnet {0} with changes {1}'.format(
+ name, str(diff)))
+ _neutron_module_call('update_subnet',
+ existing_subnet['id'],
+ **subnet_arguments)
+ changes_dict = _created(name, 'subnet', diff)
+ changes_dict['comment'] = '{1} {0} updated'.format(name, 'subnet')
+ return changes_dict
+ except:
+ LOG.exception('Could not update subnet {0}'.format(name))
+ return _update_failed(name, 'subnet')
+ return _no_change(name, 'subnet')
+
+
+@_test_call
+def subnet_absent(name, profile=None):
+ connection_args = _auth(profile)
+ existing_subnet = _neutron_module_call(
+ 'list_subnets', name=name, **connection_args)
+ if existing_subnet:
+ _neutron_module_call(
+ 'delete_subnet', existing_subnet[name]['id'], **connection_args)
+ if _neutron_module_call('list_subnets', name=name, **connection_args):
+ return _delete_failed(name, 'subnet')
+ return _deleted(name, 'subnet', existing_subnet[name])
+ return _absent(name, 'subnet')
+ return _absent(name, 'network')
+
+
+@_test_call
+def router_present(name=None,
+ gateway_network=None,
+ interfaces=None,
+ admin_state_up=None,
+ profile=None):
+ '''
+ Ensure that the neutron router is present with the specified properties.
+ name
+ The name of the subnet to manage
+ gateway_network
+ The network that would be the router's default gateway
+ interfaces
+ list of subnets the router attaches to
+ '''
+ connection_args = _auth(profile)
+ existing_router = _neutron_module_call(
+ 'list_routers', name=name, **connection_args)
+ if not existing_router:
+ _neutron_module_call('create_router', name=name, **connection_args)
+ created_router = _neutron_module_call(
+ 'list_routers', name=name, **connection_args)
+ if created_router:
+ router_id = created_router[name]['id']
+ network = _neutron_module_call(
+ 'list_networks', name=gateway_network, **connection_args)
+ gateway_network_id = network[gateway_network]['id']
+ _neutron_module_call('router_gateway_set',
+ router_id=router_id,
+ external_gateway=gateway_network_id,
+ **connection_args)
+ for interface in interfaces:
+ subnet = _neutron_module_call(
+ 'list_subnets', name=interface, **connection_args)
+ subnet_id = subnet[interface]['id']
+ _neutron_module_call('router_add_interface',
+ router_id=router_id,
+ subnet_id=subnet_id,
+ **connection_args)
+ return _created(name,
+ 'router',
+ _neutron_module_call('list_routers',
+ name=name,
+ **connection_args))
+ return _create_failed(name, 'router')
+
+ router_id = existing_router[name]['id']
+ existing_router = existing_router[name]
+ diff = {}
+ if admin_state_up and existing_router['admin_state_up'] != admin_state_up:
+ diff.update({'admin_state_up': admin_state_up})
+ if gateway_network:
+ network = _neutron_module_call(
+ 'list_networks', name=gateway_network, **connection_args)
+ gateway_network_id = network[gateway_network]['id']
+ if not existing_router['external_gateway_info'] and not existing_router['external_gateway_info'] == None:
+ if existing_router['external_gateway_info']['network_id'] != gateway_network_id:
+ diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
+ elif not 'network_id' in existing_router['external_gateway_info'] or existing_router['external_gateway_info']['network_id'] != gateway_network_id:
+ diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
+ if diff:
+ # update the changes
+ router_args = diff.copy()
+ router_args.update(connection_args)
+ try:
+ _neutron_module_call('update_router', existing_router['id'], **router_args)
+ changes_dict = _created(name, 'router', diff)
+ changes_dict['comment'] = 'Router {0} updated'.format(name)
+ return changes_dict
+ except:
+ LOG.exception('Router {0} could not be updated'.format(name))
+ return _update_failed(name, 'router')
+ return _no_change(name, 'router')
+
+def security_group_present(name=None,
+ description=None,
+ rules=[],
+ profile=None):
+ '''
+ Ensure that the security group is present with the specified properties.
+ name
+ The name of the security group
+ description
+ The description of the security group
+ rules
+ list of rules to be added to the given security group
+ '''
+ #for p in rules: print p
+ # If the user is an admin, he's able to see the security groups from
+ # other tenants. In this case, we'll use the tenant id to get an existing
+ # security group.
+ connection_args = _auth(profile)
+ tenant_name = connection_args['connection_tenant']
+ try:
+ tenant_id = __salt__['keystone.tenant_get'](
+ name=tenant_name, **connection_args)[tenant_name]['id']
+ except:
+ tenant_id = None
+ LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
+ connection_args['connection_user']))
+ if tenant_id:
+ security_group = _neutron_module_call(
+ 'list_security_groups', name=name, tenant_id=tenant_id,
+ **connection_args)
+ else:
+ security_group = _neutron_module_call(
+ 'list_security_groups', name=name, **connection_args)
+
+ if not security_group:
+ # Create the security group as it doesn't exist already.
+ security_group_id = _neutron_module_call('create_security_group',
+ name=name,
+ description=description,
+ **connection_args)
+ else:
+ security_group_id = security_group[name]['id']
+
+ # Set the missing rules attributes (in case the user didn't specify them
+ # in pillar) to some default values.
+ rules_attributes_defaults = {
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'TCP',
+ 'port_range_min': None,
+ 'port_range_max': None,
+ 'remote_ip_prefix': None
+ }
+ for rule in rules:
+ for attribute in rules_attributes_defaults.keys():
+ if not rule.has_key(attribute):
+ rule[attribute] = rules_attributes_defaults[attribute]
+
+ for rule in rules:
+ for attribute in rules_attributes_defaults.keys():
+ print rule
+ print rule[attribute]
+
+ # Remove all the duplicates rules given by the user in pillar.
+ unique_rules = []
+ for rule in rules:
+ if rule not in unique_rules:
+ unique_rules.append(rule)
+
+ #for p in unique_rules: print p
+
+ # Get the existing security group rules.
+ existing_rules = _neutron_module_call(
+ 'list_security_groups',
+ id=security_group_id,
+ **connection_args)[name]['security_group_rules']
+
+ #for p in existing_rules: print p
+
+ new_rules = {}
+ for rule in unique_rules:
+ rule_found = False
+ for existing_rule in existing_rules:
+ attributes_match = True
+ # Compare the attributes of the existing security group rule with
+ # the attributes of the rule that we want to add.
+ for attribute in rules_attributes_defaults.keys():
+ existing_attribute = '' if not existing_rule[attribute] \
+ else str(existing_rule[attribute]).lower()
+ attribute = '' if not rule[attribute] \
+ else str(rule[attribute]).lower()
+ if existing_attribute != attribute:
+ attributes_match = False
+ break
+ if attributes_match:
+ rule_found = True
+ break
+ if rule_found:
+ # Skip adding the rule as it already exists.
+ continue
+ rule_index = len(new_rules) + 1
+ new_rules.update({'Rule {0}'.format(rule_index): rule})
+ print security_group_id
+ print rule['direction']
+ print rule['ethertype']
+ print rule['protocol']
+ print rule['port_range_min']
+ print rule['port_range_max']
+ print rule['remote_ip_prefix']
+ _neutron_module_call('create_security_group_rule',
+ security_group_id=security_group_id,
+ direction=rule['direction'],
+ ethertype=rule['ethertype'],
+ protocol=rule['protocol'],
+ port_range_min=rule['port_range_min'],
+ port_range_max=rule['port_range_max'],
+ remote_ip_prefix=rule['remote_ip_prefix'],
+ **connection_args)
+
+ # print security_group_id
+ # print direction
+ # print ethertype
+ # print protocol
+ # print port_range_min
+ # print port_range_max
+ # print remote_ip_prefix
+
+ if not security_group:
+ # The security group didn't exist. It was created and specified
+ # rules were added to it.
+ security_group = _neutron_module_call('list_security_groups',
+ id=security_group_id,
+ **connection_args)[name]
+ return _created(name, 'security_group', security_group)
+ if len(new_rules) == 0:
+ # Security group already exists and specified rules are already
+ # present.
+ return _no_change(name, 'security_group')
+ # Security group already exists, but the specified rules were added to it.
+ return _updated(name, 'security_group', {'New Rules': new_rules})
+
+def _created(name, resource, resource_definition):
+ changes_dict = {'name': name,
+ 'changes': resource_definition,
+ 'result': True,
+ 'comment': '{0} {1} created'.format(resource, name)}
+ return changes_dict
+
+def _updated(name, resource, resource_definition):
+ changes_dict = {'name': name,
+ 'changes': resource_definition,
+ 'result': True,
+ 'comment': '{0} {1} updated'.format(resource, name)}
+ return changes_dict
+
+def _no_change(name, resource, test=False):
+ changes_dict = {'name': name,
+ 'changes': {},
+ 'result': True}
+ if test:
+ changes_dict['comment'] = \
+ '{0} {1} will be {2}'.format(resource, name, test)
+ else:
+ changes_dict['comment'] = \
+ '{0} {1} is in correct state'.format(resource, name)
+ return changes_dict
+
+
+def _deleted(name, resource, resource_definition):
+ changes_dict = {'name': name,
+ 'changes': {},
+ 'comment': '{0} {1} removed'.format(resource, name),
+ 'result': True}
+ return changes_dict
+
+
+def _absent(name, resource):
+ changes_dict = {'name': name,
+ 'changes': {},
+ 'comment': '{0} {1} not present'.format(resource, name),
+ 'result': True}
+ return changes_dict
+
+
+def _delete_failed(name, resource):
+ changes_dict = {'name': name,
+ 'changes': {},
+ 'comment': '{0} {1} failed to delete'.format(resource,
+ name),
+ 'result': False}
+ return changes_dict
+
+def _create_failed(name, resource):
+ changes_dict = {'name': name,
+ 'changes': {},
+ 'comment': '{0} {1} failed to create'.format(resource,
+ name),
+ 'result': False}
+ return changes_dict
+
+def _update_failed(name, resource):
+ changes_dict = {'name': name,
+ 'changes': {},
+ 'comment': '{0} {1} failed to update'.format(resource,
+ name),
+ 'result': False}
+ return changes_dict
+
+
+def _get_non_null_args(**kwargs):
+ '''
+ Return those kwargs which are not null
+ '''
+ return dict((key, value,) for key, value in kwargs.iteritems()
+ if value is not None)
\ No newline at end of file
diff --git a/neutron/client.sls b/neutron/client.sls
new file mode 100644
index 0000000..e78c35b
--- /dev/null
+++ b/neutron/client.sls
@@ -0,0 +1,91 @@
+{%- from "neutron/map.jinja" import client with context %}
+{%- if client.enabled %}
+
+neutron_client_packages:
+ pkg.installed:
+ - names: {{ client.pkgs }}
+
+
+{%- for identity_name, identity in client.server.iteritems() %}
+
+{%- for network_name, network in identity.network.iteritems() %}
+
+neutron_openstack_network_{{ network_name }}:
+ neutronng.network_present:
+ - name: {{ network_name }}
+ - profile: {{ identity_name }}
+
+ {%- if network.provider_network_type is defined %}
+ - provider_network_type: {{ network.provider_network_type }}
+ {%- endif %}
+ {%- if network.provider_physical_network is defined %}
+ - provider_physical_network: {{ network.provider_physical_network }}
+ {%- endif %}
+ {%- if network.router_external is defined %}
+ - router_external: {{ network.router_external }}
+ {%- endif %}
+ {%- if network.admin_state_up is defined %}
+ - admin_state_up: {{ network.admin_state_up }}
+ {%- endif %}
+ {%- if network.shared is defined %}
+ - shared: {{ network.shared }}
+ {%- endif %}
+ {%- if network.provider_segmentation_id is defined %}
+ - provider_segmentation_id: {{ network.provider_segmentation_id }}
+ {%- endif %}
+
+{%- for subnet_name, subnet in network.subnet.iteritems() %}
+neutron_openstack_subnet_{{ subnet_name }}:
+ neutronng.subnet_present:
+ - name: {{ subnet_name }}
+ - network: {{ network_name }}
+ - profile: {{ identity_name }}
+
+ {%- if subnet.cidr is defined %}
+ - cidr: {{ subnet.cidr }}
+ {%- endif %}
+ {%- if subnet.ip_version is defined %}
+ - ip_version: {{ subnet.ip_version }}
+ {%- endif %}
+ {%- if subnet.enable_dhcp is defined %}
+ - enable_dhcp: {{ subnet.enable_dhcp }}
+ {%- endif %}
+ {%- if subnet.allocation_pools is defined %}
+ - allocation_pools: {{ subnet.allocation_pools }}
+ {%- endif %}
+ {%- if subnet.gateway_ip is defined %}
+ - gateway_ip: {{ subnet.gateway_ip }}
+ {%- endif %}
+ {%- if subnet.dns_nameservers is defined %}
+ - dns_nameservers: {{ subnet.dns_nameservers }}
+ {%- endif %}
+ {%- if subnet.host_routes is defined %}
+ - host_routes: {{ subnet.host_routes }}
+ {%- endif %}
+ - require:
+ - neutronng: neutron_openstack_network_{{ network_name }}
+
+{%- endfor %}
+{%- endfor %}
+
+{%- for router_name, router in identity.router.iteritems() %}
+neutron_openstack_router_{{ router_name }}:
+ neutronng.router_present:
+ - name: {{ router_name }}
+ - interfaces: {{ router.interfaces }}
+ - gateway_network: {{ router.gateway_network }}
+ - profile: {{ identity_name }}
+{%- endfor %}
+
+{%- for security_group_name, security_group in identity.security_group.iteritems() %}
+openstack_security_group_{{ security_group_name }}:
+ neutronng.security_group_present:
+ - name: {{ security_group_name }}
+ - description: {{ security_group.description }}
+ - rules: {{ security_group.rules }}
+ - profile: {{ identity_name }}
+{%- endfor %}
+
+{%- endfor %}
+
+{%- endif %}
diff --git a/neutron/init.sls b/neutron/init.sls
index 8e39f6f..c12f434 100644
--- a/neutron/init.sls
+++ b/neutron/init.sls
@@ -8,4 +8,7 @@
{% endif %}
{% if pillar.neutron.compute is defined %}
- neutron.compute
+{% endif %}
+{% if pillar.neutron.client is defined %}
+- neutron.client
{% endif %}
\ No newline at end of file
diff --git a/neutron/map.jinja b/neutron/map.jinja
index 1af2a68..ede8d16 100644
--- a/neutron/map.jinja
+++ b/neutron/map.jinja
@@ -48,6 +48,15 @@
},
}, merge=pillar.neutron.get('server', {})) %}
+{% set client = salt['grains.filter_by']({
+ 'Debian': {
+ 'pkgs': ['python-neutronclient']
+ },
+ 'RedHat': {
+ 'pkgs': ['python-neutronclient']
+ },
+}, merge=pillar.neutron.get('client', {})) %}
+
{%- if pillar.neutron.server is defined %}
{%- set tmp_server = pillar.neutron.server %}