Skip to content

Commit

Permalink
Merge pull request quattor#83 in AQUILON_AQD/aqd from ~AQBLD/aqd:for_…
Browse files Browse the repository at this point in the history
…merge/master/by_topic/shared_sa to master

* commit 'b3aee3eae3a02513cfbc7ab1657740dc6cd1f11c':
  Support for shared-service-name resources in resourcegroups
  • Loading branch information
Fred Barnes authored and Fred Barnes committed Nov 15, 2019
2 parents eb504c8 + b3aee3e commit cd072b9
Show file tree
Hide file tree
Showing 16 changed files with 579 additions and 10 deletions.
33 changes: 33 additions & 0 deletions etc/input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6362,6 +6362,39 @@
<transport method="delete" path="resource/intervention/%(intervention)s"/>
</command>

<command name="add_shared_service_name">
Add a new shared service name resource.
<optgroup mandatory="True" fields="all">
<option name="resourcegroup" type="string">Resourcegroup to apply resource</option>
<option name="name" type="string">Name of the resource</option>
<option name="fqdn" type="string">Fully qualified domain name</option>
<option name="sa_aliases" type="boolean">Automatically create address aliases for service addresses</option>
</optgroup>
<optgroup>
<option name="dns_environment" type="string">The name of the environment the FQDN is in (defaults to 'internal')</option>
<option name="comments" type="string">Comments</option>

<option name="justification" type="string">Authorization tokens (e.g. TCM number or "emergency") to validate the request</option>
<option name="reason" type="string">Human readable description of why the operation was performed</option>
<option name="cm_check" type="flag">Do a dry-run, and report the objects in-scope for change-management.</option>
</optgroup>
<transport method="put" path="resource/shared_service_name/%(name)s" />
</command>

<command name="del_shared_service_name">
Delete a shared service name resource.
<optgroup mandatory="True" fields="all">
<option name="resourcegroup" type="string">Resourcegroup to apply resource</option>
<option name="name" type="string">Name of the resource</option>
</optgroup>
<optgroup>
<option name="justification" type="string">Authorization tokens (e.g. TCM number or "emergency") to validate the request</option>
<option name="reason" type="string">Human readable description of why the operation was performed</option>
<option name="cm_check" type="flag">Do a dry-run, and report the objects in-scope for change-management.</option>
</optgroup>
<transport method="delete" path="resource/shared_service_name/%(name)s" />
</command>

<command name="add_service_address">
Add a new service address resource.
<optgroup mandatory="True" fields="all">
Expand Down
1 change: 1 addition & 0 deletions lib/aquilon/aqdb/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
SystemList, AutoStartList)

from aquilon.aqdb.model.service_instance_server import ServiceInstanceServer
from aquilon.aqdb.model.shared_service_name import SharedServiceName

# ENTITLEMENTS
from aquilon.aqdb.model.entitlement import (
Expand Down
12 changes: 11 additions & 1 deletion lib/aquilon/aqdb/model/dns_record.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- cpy-indent-level: 4; indent-tabs-mode: nil -*-
# ex: set expandtab softtabstop=4 shiftwidth=4:
#
# Copyright (C) 2011,2012,2013,2014,2015,2016,2018 Contributor
# Copyright (C) 2011-2016,2018-2019 Contributor
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -264,6 +264,16 @@ def __init__(self, fqdn=None, require_grn=True, **kwargs):
if existing.dns_record_type in _rr_conflict_map[own_type]:
raise ArgumentError("{0} already exist.".format(existing))

# If the FQDN is used in a shared_service_name resource, ensure
# that only an address-alias records can be created, iff sa_aliases
# is set.
if fqdn.shared_service_names:
ssnres = fqdn.shared_service_names[0]

if not ssnres.sa_aliases or own_type != 'address_alias':
raise ArgumentError("{0} already exists with the same "
"FQDN.".format(ssnres))

self.fqdn = fqdn
self._require_grn = require_grn

Expand Down
57 changes: 57 additions & 0 deletions lib/aquilon/aqdb/model/shared_service_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- cpy-indent-level: 4; indent-tabs-mode: nil -*-
# ex: set expandtab softtabstop=4 shiftwidth=4:
#
# Copyright (C) 2019 Contributor
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from sqlalchemy import (
Boolean,
Column,
ForeignKey,
)
from sqlalchemy.orm import (
backref,
relation,
)

from aquilon.aqdb.model import (
Fqdn,
Resource,
)

_TN = 'shared_sn'


class SharedServiceName(Resource):
"""Shared service name resources"""
__tablename__ = _TN

resource_id = Column(ForeignKey(Resource.id, ondelete='CASCADE',
name='shared_sn_resource_id_fk'),
primary_key=True)

# if true, indicates that address-aliases should be created from the FQDN
# to particular service addresses in the same resourcegroup.
sa_aliases = Column(Boolean, nullable=False)

# FQDN is the 'shared service name' that is chosen -- must be a valid name
# that the address-alias records can be created against
fqdn_id = Column(ForeignKey(Fqdn.id), nullable=False, index=True)

fqdn = relation(Fqdn, lazy=False, innerjoin=True,
backref=backref('shared_service_names'))

__table_args__ = ({'info': {'unique_fields': ['name', 'holder']}},)

__mapper_args__ = {'polymorphic_identity': _TN}
80 changes: 80 additions & 0 deletions lib/aquilon/worker/commands/add_shared_service_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- cpy-indent-level: 4; indent-tabs-mode: nil -*-
# ex: set expandtab softtabstop=4 shiftwidth=4:
#
# Copyright (C) 2019 Contributor
#
# 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.
"""Contains the logic for `aq add shared service name`."""

from aquilon.exceptions_ import ArgumentError
from aquilon.worker.broker import BrokerCommand # noqa
from aquilon.worker.commands.add_resource import CommandAddResource
from aquilon.aqdb.model import (
AddressAlias,
BundleResource,
Fqdn,
ResourceGroup,
SharedServiceName,
)


class CommandAddSharedServiceName(CommandAddResource):

required_parameters = ['fqdn', 'name', 'resourcegroup', 'sa_aliases']
resource_class = SharedServiceName
resource_name = 'name'

def render(self, **kwargs):
# This command deliberately does not allow the resource holder to
# be specified as anything other than a resource-group, so fill in
# the missing ones here.
for h in ('hostname', 'cluster', 'metacluster', 'personality',
'archetype', 'eon_id', 'grn'):
kwargs[h] = None

super(CommandAddSharedServiceName, self).render(**kwargs)

def setup_resource(self, session, logger, dbres, reason,
fqdn, sa_aliases, dns_environment, **_):
# ensure this belongs in a resource-group; use the fact the
# holder is already determined
if (not isinstance(dbres.holder, BundleResource) or
not isinstance(dbres.holder.resourcegroup, ResourceGroup)):
raise ArgumentError("{0} must be in a resource-group "
"(got {1:c})".format(dbres, dbres.holder))

# ensure no other shared-service-name resource than ourself is present
# in the group.
for res in dbres.holder.resources:
if isinstance(res, SharedServiceName) and (res != dbres):
raise ArgumentError("{0} already has a {1:c} resource".
format(dbres.holder.resourcegroup, res))

dbfqdn = Fqdn.get_or_create(session, dns_environment=dns_environment,
fqdn=fqdn)

# if sa_aliases is true, ensure the FQDN is unique except for
# address-aliases; if sa_aliases is false, no DNS records using
# that (LHS) may exist.
for dnsrec in dbfqdn.dns_records:
if not sa_aliases or not isinstance(dnsrec, AddressAlias):
raise ArgumentError("{0:s} cannot be used as a shared "
"service name FQDN, as {1} already "
"exists".format(fqdn, dnsrec))
if dbfqdn.shared_service_names:
raise ArgumentError("{0:s} cannot be used as a shared service "
"name FQDN, as {1} already exists"
.format(fqdn, dbfqdn.shared_service_names[0]))

dbres.sa_aliases = sa_aliases
dbres.fqdn = dbfqdn
55 changes: 55 additions & 0 deletions lib/aquilon/worker/commands/del_shared_service_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -*- cpy-indent-level: 4; indent-tabs-mode: nil -*-
# ex: set expandtab softtabstop=4 shiftwidth=4:
#
# Copyright (C) 2019 Contributor
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from aquilon.aqdb.model import (
DnsRecord,
SharedServiceName,
)
from aquilon.worker.broker import BrokerCommand
from aquilon.worker.dbwrappers.change_management import ChangeManagement
from aquilon.worker.dbwrappers.resources import get_resource_holder


class CommandDelSharedServiceName(BrokerCommand):
required_parameters = ['name', 'resourcegroup']
resource_class = SharedServiceName
resource_name = 'name'

def render(self, session, logger, plenaries, name,
resourcegroup, user, justification, reason, **kwargs):
holder = get_resource_holder(session, logger, resgroup=resourcegroup,
compel=False)

# Validate change-management
cm = ChangeManagement(session, user, justification, reason,
logger, self.command, **kwargs)
cm.consider(holder)
cm.validate()

dbssn = SharedServiceName.get_unique(session, name=name,
holder=holder, compel=True)

holder.resources.remove(dbssn)

# delete the FQDN if it is orphaned
dbfqdn = dbssn.fqdn

q = session.query(DnsRecord)
q = q.filter_by(fqdn=dbfqdn)

if q.count() == 0:
session.delete(dbfqdn)
39 changes: 30 additions & 9 deletions lib/aquilon/worker/dbwrappers/dns.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python
# -*- cpy-indent-level: 4; indent-tabs-mode: nil -*-
# ex: set expandtab softtabstop=4 shiftwidth=4:
#
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 Contributor
# Copyright (C) 2008-2019 Contributor
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -23,10 +24,21 @@
from sqlalchemy.sql import or_

from aquilon.exceptions_ import ArgumentError, AquilonError, NotFoundException
from aquilon.aqdb.model import (Fqdn, DnsDomain, DnsRecord, ARecord,
DynamicStub, Alias, ReservedName, SrvRecord,
DnsEnvironment, AddressAssignment,
NetworkEnvironment, AddressAlias)
from aquilon.aqdb.model import (
AddressAlias,
AddressAssignment,
Alias,
ARecord,
DnsDomain,
DnsEnvironment,
DnsRecord,
DynamicStub,
Fqdn,
NetworkEnvironment,
ReservedName,
SharedServiceName,
SrvRecord,
)
from aquilon.aqdb.model.dns_domain import parse_fqdn
from aquilon.aqdb.model.network import get_net_id_from_ip
from aquilon.worker.dbwrappers.interface import check_ip_restrictions
Expand Down Expand Up @@ -100,7 +112,12 @@ def delete_dns_record(dbdns_rec, locked=False, verify_assignments=False, exporte
# Delete the FQDN if it is orphaned
q = session.query(DnsRecord)
q = q.filter_by(fqdn=dbfqdn)
if q.count() == 0:

# Also check for references in SharedServiceName resources
q2 = session.query(SharedServiceName)
q2 = q2.filter_by(fqdn=dbfqdn)

if q.count() == 0 and q2.count() == 0:
session.delete(dbfqdn)
if exporter:
exporter.delete(dbfqdn)
Expand Down Expand Up @@ -456,16 +473,20 @@ def delete_target_if_needed(session, dbtarget, exporter=None):
else:
dbdns_rec = None

# Check if the FQDN is still the target of an existing alias, service record
# or reverse PTR record
# Check if the FQDN is still the target of an existing alias, service
# record, reverse PTR record or SharedServiceName
q = session.query(DnsRecord)
q = q.with_polymorphic([ARecord, Alias, AddressAlias, SrvRecord])
q = q.filter(or_(ARecord.reverse_ptr_id == dbtarget.id,
Alias.target_id == dbtarget.id,
AddressAlias.target_id == dbtarget.id,
SrvRecord.target_id == dbtarget.id))
q = q.options(lazyload('fqdn'))
if not q.count():

q2 = session.query(SharedServiceName)
q2 = q2.filter_by(fqdn=dbtarget)

if not q.count() and not q2.count():
for dbdns_rec in dbtarget.dns_records:
session.delete(dbdns_rec)
if exporter:
Expand Down
32 changes: 32 additions & 0 deletions lib/aquilon/worker/formats/shared_service_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- cpy-indent-level: 4; indent-tabs-mode: nil -*-
# ex: set expandtab softtabstop=4 shiftwidth=4:
#
# Copyright (C) 2019 Contributor
#
# 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.
"""SharedServiceName Resource formatter."""

from aquilon.aqdb.model import SharedServiceName
from aquilon.worker.formats.formatters import ObjectFormatter
from aquilon.worker.formats.resource import ResourceFormatter


class SharedServiceNameFormatter(ResourceFormatter):
def extra_details(self, pn, indent=""):
details = []
details.append(indent + " FQDN: %s" % pn.fqdn)
details.append(indent + " SA Aliases: %s" % pn.sa_aliases)
return details


ObjectFormatter.handlers[SharedServiceName] = SharedServiceNameFormatter()
2 changes: 2 additions & 0 deletions lib/aquilon/worker/templates/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
ResourceGroup,
ServiceAddress,
Share,
SharedServiceName,
SystemList,
VirtualMachine,
)
Expand Down Expand Up @@ -243,6 +244,7 @@ def body_system_list(self, lines):
Plenary.handlers[Filesystem] = PlenaryResource
Plenary.handlers[Intervention] = PlenaryResource
Plenary.handlers[Hostlink] = PlenaryResource
Plenary.handlers[SharedServiceName] = PlenaryResource
Plenary.handlers[RebootSchedule] = PlenaryResource
Plenary.handlers[RebootIntervention] = PlenaryResource
Plenary.handlers[ServiceAddress] = PlenaryResource
Expand Down
Loading

0 comments on commit cd072b9

Please sign in to comment.