diff options
Diffstat (limited to 'auth')
33 files changed, 561 insertions, 339 deletions
diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Approval.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Approval.java index 1bc82f5e..dc96a1ce 100644 --- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Approval.java +++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Approval.java @@ -211,6 +211,13 @@ public class Approval implements CacheChange.Data { } } + public static void clear() { + byApprover.clear(); + byUser.clear(); + byTicket.clear(); + list.clear(); + cache.resetLocalData(); + } // public void update(AuthzTrans trans, ApprovalDAO apprDAO, boolean dryRun) { // if (dryRun) { // trans.info().printf("Would update Approval %s, %s, last_notified %s",add.id,add.status,add.last_notified); diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java index e51fcfdc..f5669331 100644 --- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java +++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Cred.java @@ -372,4 +372,10 @@ public class Cred { } return reason; } + + + public static void clear() { + data.clear(); + byNS.clear(); + } }
\ No newline at end of file diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Role.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Role.java index ea735d2a..bb5e8c21 100644 --- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Role.java +++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/Role.java @@ -179,4 +179,11 @@ public class Role implements Comparable<Role> { deleteRoles.clear(); } + public static void clear() { + data.clear(); + keys.clear(); + byName.clear(); + deleteRoles.clear(); + } + }
\ No newline at end of file diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java index a26da912..b4e1a6d3 100644 --- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java +++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/helpers/UserRole.java @@ -354,4 +354,12 @@ public class UserRole implements Cloneable, CacheChange.Data { public static String histSubject(List<String> row) { return row.get(1) + '|' + row.get(2); } + + public static void clear() { + data.clear(); + byUser.clear(); + byRole.clear(); + cache.resetLocalData(); + + } }
\ No newline at end of file diff --git a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java index 0d5ad47c..60902f1e 100644 --- a/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java +++ b/auth/auth-batch/src/main/java/org/onap/aaf/auth/batch/reports/Analyze.java @@ -106,8 +106,6 @@ public class Analyze extends Batch { tt.done(); } - // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway - Cred.load(trans, session); minOwners=1; @@ -149,8 +147,6 @@ public class Analyze extends Batch { writerList.put(EXTEND,extendCW); // Load full data of the following - Approval.load(trans, session, Approval.v2_0_17); - Role.load(trans, session); ln = new LastNotified(session); } finally { @@ -160,335 +156,356 @@ public class Analyze extends Batch { @Override protected void run(AuthzTrans trans) { + TimeTaken tt; AuthzTrans noAvg = trans.env().newTransNoAvg(); //////////////////// // Load all Notifieds, and either add to local Data, or mark for Deletion. ln.loadAll(noAvg,expireRange.approveDelete,deleteCW); - //////////////////// - final Map<UUID,Ticket> goodTickets = new TreeMap<>(); - TimeTaken tt = trans.start("Analyze Expired Futures",Trans.SUB); + // Hold Good Tickets to keyed User/Role for UserRole Step + Map<String,Ticket> mur = new TreeMap<>(); + try { - Future.load(noAvg, session, Future.withConstruct, fut -> { - List<Approval> appls = Approval.byTicket.get(fut.id()); - if(!futureRange.inRange(fut.expires())) { - deleteCW.comment("Future %s expired", fut.id()); - Future.row(deleteCW,fut); - if(appls!=null) { - for(Approval a : appls) { - Approval.row(deleteCW, a); + Approval.load(trans, session, Approval.v2_0_17); + + //////////////////// + final Map<UUID,Ticket> goodTickets = new TreeMap<>(); + tt = trans.start("Analyze Expired Futures",Trans.SUB); + try { + Future.load(noAvg, session, Future.withConstruct, fut -> { + List<Approval> appls = Approval.byTicket.get(fut.id()); + if(!futureRange.inRange(fut.expires())) { + deleteCW.comment("Future %s expired", fut.id()); + Future.row(deleteCW,fut); + if(appls!=null) { + for(Approval a : appls) { + Approval.row(deleteCW, a); + } } + } else if(appls==null) { // Orphaned Future (no Approvals) + deleteCW.comment("Future is Orphaned"); + Future.row(deleteCW,fut); + } else { + goodTickets.put(fut.fdd.id, new Ticket(fut)); + } + }); + } finally { + tt.done(); + } + + Set<String> approvers = new TreeSet<>(); + tt = trans.start("Connect Approvals with Futures",Trans.SUB); + try { + for(Approval appr : Approval.list) { + Ticket ticket=null; + UUID ticketID = appr.getTicket(); + if(ticketID!=null) { + ticket = goodTickets.get(appr.getTicket()); + } + if(ticket == null) { // Orphaned Approvals, no Futures + deleteCW.comment("Approval is Orphaned"); + Approval.row(deleteCW, appr); + } else { + ticket.approvals.add(appr); // add to found Ticket + approvers.add(appr.getApprover()); } - } else if(appls==null) { // Orphaned Future (no Approvals) - deleteCW.comment("Future is Orphaned"); - Future.row(deleteCW,fut); - } else { - goodTickets.put(fut.fdd.id, new Ticket(fut)); - } - }); - } finally { - tt.done(); - } - - Set<String> approvers = new TreeSet<>(); - tt = trans.start("Connect Approvals with Futures",Trans.SUB); - try { - for(Approval appr : Approval.list) { - Ticket ticket=null; - UUID ticketID = appr.getTicket(); - if(ticketID!=null) { - ticket = goodTickets.get(appr.getTicket()); - } - if(ticket == null) { // Orphaned Approvals, no Futures - deleteCW.comment("Approval is Orphaned"); - Approval.row(deleteCW, appr); - } else { - ticket.approvals.add(appr); // add to found Ticket - approvers.add(appr.getApprover()); } - } - } finally { - tt.done(); - } - - /* Run through all Futures, and see if - * 1) they have been executed (no longer valid) - * 2) The current Approvals indicate they can proceed - */ - Map<String,Pending> pendingApprs = new HashMap<>(); - Map<String,Pending> pendingTemp = new HashMap<>(); - - // Convert Good Tickets to keyed User/Role for UserRole Step - Map<String,Ticket> mur = new TreeMap<>(); - String approver; - - tt = trans.start("Analyze Good Tickets",Trans.SUB); - try { - for(Ticket ticket : goodTickets.values()) { - try { - pendingTemp.clear(); - switch(ticket.f.target()) { - case "user_role": - int state[][] = new int[3][3]; - int type; - - for(Approval appr : ticket.approvals) { - switch(appr.getType()) { - case "owner": - type=owner; - break; - case "supervisor": - type=supervisor; - break; - default: - type=0; + } finally { + tt.done(); + } + + /* Run through all Futures, and see if + * 1) they have been executed (no longer valid) + * 2) The current Approvals indicate they can proceed + */ + Map<String,Pending> pendingApprs = new HashMap<>(); + Map<String,Pending> pendingTemp = new HashMap<>(); + + String approver; + + tt = trans.start("Analyze Good Tickets",Trans.SUB); + try { + for(Ticket ticket : goodTickets.values()) { + try { + pendingTemp.clear(); + switch(ticket.f.target()) { + case "user_role": + int state[][] = new int[3][3]; + int type; + + for(Approval appr : ticket.approvals) { + switch(appr.getType()) { + case "owner": + type=owner; + break; + case "supervisor": + type=supervisor; + break; + default: + type=0; + } + ++state[type][total]; // count per type + switch(appr.getStatus()) { + case "pending": + ++state[type][pending]; + approver = appr.getApprover(); + Pending n = pendingTemp.get(approver); + if(n==null) { + Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key); + pendingTemp.put(approver,new Pending(lastNotified)); + } else { + n.inc(); + } + break; + case "approved": + ++state[type][approved]; + break; + default: + ++state[type][unknown]; + } } - ++state[type][total]; // count per type - switch(appr.getStatus()) { - case "pending": - ++state[type][pending]; - approver = appr.getApprover(); - Pending n = pendingTemp.get(approver); - if(n==null) { - Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key); - pendingTemp.put(approver,new Pending(lastNotified)); - } else { - n.inc(); + + // To Approve: + // Always must have at least 1 owner + if((state[owner][total]>0 && state[owner][approved]>0) && + // If there are no Supervisors, that's ok + (state[supervisor][total]==0 || + // But if there is a Supervisor, they must have approved + (state[supervisor][approved]>0))) { + UserRoleDAO.Data urdd = new UserRoleDAO.Data(); + try { + urdd.reconstitute(ticket.f.fdd.construct); + if(urdd.expires.before(ticket.f.expires())) { + extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires()); + } + } catch (IOException e) { + trans.error().log("Could not reconstitute UserRole"); } - break; - case "approved": - ++state[type][approved]; - break; - default: - ++state[type][unknown]; - } - } - - // To Approve: - // Always must have at least 1 owner - if((state[owner][total]>0 && state[owner][approved]>0) && - // If there are no Supervisors, that's ok - (state[supervisor][total]==0 || - // But if there is a Supervisor, they must have approved - (state[supervisor][approved]>0))) { - UserRoleDAO.Data urdd = new UserRoleDAO.Data(); - try { - urdd.reconstitute(ticket.f.fdd.construct); - if(urdd.expires.before(ticket.f.expires())) { - extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires()); + } else { // Load all the Pending. + for(Entry<String, Pending> es : pendingTemp.entrySet()) { + Pending p = pendingApprs.get(es.getKey()); + if(p==null) { + pendingApprs.put(es.getKey(), es.getValue()); + } else { + p.inc(es.getValue()); } - } catch (IOException e) { - trans.error().log("Could not reconstitute UserRole"); - } - } else { // Load all the Pending. - for(Entry<String, Pending> es : pendingTemp.entrySet()) { - Pending p = pendingApprs.get(es.getKey()); - if(p==null) { - pendingApprs.put(es.getKey(), es.getValue()); - } else { - p.inc(es.getValue()); } } + break; + } + } finally { + if("user_role".equals(ticket.f.fdd.target)) { + String key = ticket.f.fdd.target_key; + if(key!=null) { + mur.put(key, ticket); } - break; - } - } finally { - if("user_role".equals(ticket.f.fdd.target)) { - String key = ticket.f.fdd.target_key; - if(key!=null) { - mur.put(key, ticket); } } } + } finally { + tt.done(); } - } finally { - tt.done(); - } - - // Good Tickets no longer needed - goodTickets.clear(); - - /** - * Decide to Notify about Approvals, based on activity/last Notified - */ - tt = trans.start("Analyze Approval Reminders", Trans.SUB); - try { - GregorianCalendar gc = new GregorianCalendar(); - gc.add(GregorianCalendar.DAY_OF_WEEK, 5); - Date remind = gc.getTime(); - - for(Entry<String, Pending> es : pendingApprs.entrySet()) { - Pending p = es.getValue(); - if(p.newApprovals() - || p.earliest() == null - || p.earliest().after(remind)) { - p.row(needApproveCW,es.getKey()); + // Good Tickets no longer needed + goodTickets.clear(); + + /** + * Decide to Notify about Approvals, based on activity/last Notified + */ + tt = trans.start("Analyze Approval Reminders", Trans.SUB); + try { + GregorianCalendar gc = new GregorianCalendar(); + gc.add(GregorianCalendar.DAY_OF_WEEK, 5); + Date remind = gc.getTime(); + + for(Entry<String, Pending> es : pendingApprs.entrySet()) { + Pending p = es.getValue(); + if(p.newApprovals() + || p.earliest() == null + || p.earliest().after(remind)) { + p.row(needApproveCW,es.getKey()); + } } + } finally { + tt.done(); } - } finally { - tt.done(); - } - - // clear out Approval Intermediates - pendingTemp = null; - pendingApprs = null; - + + // clear out Approval Intermediates + pendingTemp = null; + pendingApprs = null; + } finally { + Approval.clear(); + } + /** Run through User Roles. Owners are treated specially in next section. Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file. - */ - try { - tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB); - Set<String> specialCommented = new HashSet<>(); - Map<String, Set<UserRole>> owners = new TreeMap<>(); - try { - UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> { - Identity identity; - try { - identity = trans.org().getIdentity(noAvg,ur.user()); - if(identity==null) { - // Candidate for Delete, but not Users if Special - String id = ur.user(); - for(String s : specialDomains) { - if(id.endsWith(s)) { + */ + + try { + Role.load(trans, session); + + try { + tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB); + Set<String> specialCommented = new HashSet<>(); + Map<String, Set<UserRole>> owners = new TreeMap<>(); + try { + UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> { + Identity identity; + try { + identity = trans.org().getIdentity(noAvg,ur.user()); + if(identity==null) { + // Candidate for Delete, but not Users if Special + String id = ur.user(); + for(String s : specialDomains) { + if(id.endsWith(s)) { + if(!specialCommented.contains(id)) { + deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s); + specialCommented.add(id); + } + return; + } + } + if(specialNames.contains(id)) { if(!specialCommented.contains(id)) { - deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s); + deleteCW.comment("ID %s is a special ID (UR Org Check)", id); specialCommented.add(id); } return; } - } - if(specialNames.contains(id)) { - if(!specialCommented.contains(id)) { - deleteCW.comment("ID %s is a special ID (UR Org Check)", id); - specialCommented.add(id); - } + ur.row(deleteCW, UserRole.UR,"Not in Organization"); + return; + } else if(Role.byName.get(ur.role())==null) { + ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role())); return; } - ur.row(deleteCW, UserRole.UR,"Not in Organization"); - return; - } else if(Role.byName.get(ur.role())==null) { - ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role())); - return; - } - // Just let expired UserRoles sit until deleted - if(futureRange.inRange(ur.expires())) { - if(!mur.containsKey(ur.user() + '|' + ur.role())) { - // Cannot just delete owners, unless there is at least one left. Process later - if ("owner".equals(ur.rname())) { - Set<UserRole> urs = owners.get(ur.role()); - if (urs == null) { - urs = new HashSet<UserRole>(); - owners.put(ur.role(), urs); - } - urs.add(ur); - } else { - Range r = writeAnalysis(noAvg,ur); - if(r!=null) { - Approval existing = findApproval(ur); - if(existing==null) { - ur.row(needApproveCW,UserRole.APPROVE_UR); + // Just let expired UserRoles sit until deleted + if(futureRange.inRange(ur.expires())) { + if(!mur.containsKey(ur.user() + '|' + ur.role())) { + // Cannot just delete owners, unless there is at least one left. Process later + if ("owner".equals(ur.rname())) { + Set<UserRole> urs = owners.get(ur.role()); + if (urs == null) { + urs = new HashSet<UserRole>(); + owners.put(ur.role(), urs); + } + urs.add(ur); + } else { + Range r = writeAnalysis(noAvg,ur); + if(r!=null) { + Approval existing = findApproval(ur); + if(existing==null) { + ur.row(needApproveCW,UserRole.APPROVE_UR); + } } } } } + } catch (OrganizationException e) { + noAvg.error().log(e); } - } catch (OrganizationException e) { - noAvg.error().log(e); - } - }); - } finally { - tt.done(); - } - - /** - Now Process Owners, one owner Role at a time, ensuring one is left, - preferably a good one. If so, process the others as normal. - - Otherwise, write to ExpiredOwners Report - */ - tt = trans.start("Analyze Owners Separately",Trans.SUB); - try { - if (!owners.values().isEmpty()) { - File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV); - final CSV ownerCSV = new CSV(env.access(),file); - CSV.Writer expOwner = ownerCSV.writer(); - expOwner.row(INFO,EXPIRED_OWNERS,sdate,2); - - try { - for (Set<UserRole> sur : owners.values()) { - int goodOwners = 0; - for (UserRole ur : sur) { - if (ur.expires().after(now)) { - ++goodOwners; - } - } + }); + } finally { + tt.done(); + } + mur.clear(); + + /** + Now Process Owners, one owner Role at a time, ensuring one is left, + preferably a good one. If so, process the others as normal. + + Otherwise, write to ExpiredOwners Report + */ + tt = trans.start("Analyze Owners Separately",Trans.SUB); + try { + if (!owners.values().isEmpty()) { + File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV); + final CSV ownerCSV = new CSV(env.access(),file); + CSV.Writer expOwner = ownerCSV.writer(); + expOwner.row(INFO,EXPIRED_OWNERS,sdate,2); - for (UserRole ur : sur) { - if (goodOwners >= minOwners) { - Range r = writeAnalysis(noAvg, ur); - if(r!=null) { + try { + for (Set<UserRole> sur : owners.values()) { + int goodOwners = 0; + for (UserRole ur : sur) { + if (ur.expires().after(now)) { + ++goodOwners; + } + } + + for (UserRole ur : sur) { + if (goodOwners >= minOwners) { + Range r = writeAnalysis(noAvg, ur); + if(r!=null) { + Approval existing = findApproval(ur); + if(existing==null) { + ur.row(needApproveCW,UserRole.APPROVE_UR); + } + } + } else { + expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires())); Approval existing = findApproval(ur); if(existing==null) { ur.row(needApproveCW,UserRole.APPROVE_UR); } } - } else { - expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires())); - Approval existing = findApproval(ur); - if(existing==null) { - ur.row(needApproveCW,UserRole.APPROVE_UR); - } } } - } - } finally { - if(expOwner!=null) { - expOwner.close(); + } finally { + if(expOwner!=null) { + expOwner.close(); + } } } - } - } finally { - tt.done(); - } + } finally { + tt.done(); + } + } finally { + Role.clear(); + UserRole.clear(); + } /** * Check for Expired Credentials - * - * */ - tt = trans.start("Analyze Expired Credentials",Trans.SUB); try { - for (Cred cred : Cred.data.values()) { - List<Instance> linst = cred.instances; - if(linst!=null) { - Instance lastBath = null; - for(Instance inst : linst) { - // if(inst.attn>0) { - // writeAnalysis(trans, cred, inst); - // // Special Behavior: only eval the LAST Instance - // } else - // All Creds go through Life Cycle - if(deleteDate!=null && inst.expires.before(deleteDate)) { - writeAnalysis(noAvg, cred, inst); // will go to Delete - // Basic Auth has Pre-EOL notifications IF there is no Newer Credential - } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) { - if(lastBath==null || lastBath.expires.before(inst.expires)) { - lastBath = inst; - } - } - } - if(lastBath!=null) { - writeAnalysis(noAvg, cred, lastBath); + // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway + Cred.load(trans, session); + + tt = trans.start("Analyze Expired Credentials",Trans.SUB); + try { + for (Cred cred : Cred.data.values()) { + List<Instance> linst = cred.instances; + if(linst!=null) { + Instance lastBath = null; + for(Instance inst : linst) { + // if(inst.attn>0) { + // writeAnalysis(trans, cred, inst); + // // Special Behavior: only eval the LAST Instance + // } else + // All Creds go through Life Cycle + if(deleteDate!=null && inst.expires.before(deleteDate)) { + writeAnalysis(noAvg, cred, inst); // will go to Delete + // Basic Auth has Pre-EOL notifications IF there is no Newer Credential + } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) { + if(lastBath==null || lastBath.expires.before(inst.expires)) { + lastBath = inst; + } + } + } + if(lastBath!=null) { + writeAnalysis(noAvg, cred, lastBath); + } } - } + } + } finally { + tt.done(); } } finally { - tt.done(); + Cred.clear(); } - + //////////////////// tt = trans.start("Analyze Expired X509s",Trans.SUB); try { diff --git a/auth/auth-cass/src/main/java/org/onap/aaf/auth/direct/DirectAAFLocator.java b/auth/auth-cass/src/main/java/org/onap/aaf/auth/direct/DirectAAFLocator.java index cc9ee66c..81debc05 100644 --- a/auth/auth-cass/src/main/java/org/onap/aaf/auth/direct/DirectAAFLocator.java +++ b/auth/auth-cass/src/main/java/org/onap/aaf/auth/direct/DirectAAFLocator.java @@ -74,7 +74,7 @@ public class DirectAAFLocator extends AbsAAFLocator<AuthzTrans> { try { RegistrationPropHolder rph = new RegistrationPropHolder(access,0); - String aaf_url = rph.replacements("https://"+Config.AAF_LOCATE_URL_TAG+"/%CNS."+name, null,null); + String aaf_url = rph.replacements(getClass().getSimpleName(),"https://"+Config.AAF_LOCATE_URL_TAG+"/%CNS."+name, null,null); //access.getProperty("/locate/"+name+':'+version; access.printf(Level.INIT,"Creating DirectAAFLocator to %s",aaf_url); uri = new URI(aaf_url); diff --git a/auth/docker/Dockerfile.client b/auth/docker/Dockerfile.agent index b62f7b4f..b62f7b4f 100644 --- a/auth/docker/Dockerfile.client +++ b/auth/docker/Dockerfile.agent diff --git a/auth/docker/Dockerfile.hello b/auth/docker/Dockerfile.hello new file mode 100644 index 00000000..0d2a062c --- /dev/null +++ b/auth/docker/Dockerfile.hello @@ -0,0 +1,34 @@ +######### +# ============LICENSE_START==================================================== +# org.onap.aaf +# =========================================================================== +# Copyright (c) 2017 AT&T Intellectual Property. 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. +# ============LICENSE_END==================================================== +# +FROM ${DOCKER_REPOSITORY}/onap/aaf/aaf_core:${AAF_VERSION} +MAINTAINER AAF Team, AT&T 2018 +ENV VERSION=${AAF_VERSION} + +LABEL description="aaf_hello" +LABEL version=${AAF_VERSION} + +COPY bin/pod_wait.sh /opt/app/aaf/bin/ +COPY etc /opt/app/osaaf/etc +RUN mkdir -p /opt/app/aaf/status +RUN if [ -n "${DUSER}" ]; then chown ${DUSER}:${DUSER} /opt/app/aaf/status \ + && chown ${DUSER}:${DUSER} /opt/app/osaaf \ + && chown -R ${DUSER}:${DUSER} /opt/app/aaf; fi + +CMD [] diff --git a/auth/docker/dbuild.sh b/auth/docker/dbuild.sh index b2a5d510..1b4e5eee 100755 --- a/auth/docker/dbuild.sh +++ b/auth/docker/dbuild.sh @@ -76,7 +76,6 @@ cp auth-cmd/target/aaf-auth-cmd-$VERSION-full.jar sample/bin cp auth-batch/target/aaf-auth-batch-$VERSION-full.jar sample/bin cp -Rf ../conf/CA sample - # AAF Config image (for AAF itself) sed -e 's/${AAF_VERSION}/'${VERSION}'/g' \ -e 's/${AAF_COMPONENT}/'${AAF_COMPONENT}'/g' \ @@ -93,7 +92,7 @@ sed -e 's/${AAF_VERSION}/'${VERSION}'/g' \ -e 's/${AAF_COMPONENT}/'${AAF_COMPONENT}'/g' \ -e 's/${DOCKER_REPOSITORY}/'${DOCKER_REPOSITORY}'/g' \ -e 's/${DUSER}/'${DUSER}'/g' \ - docker/Dockerfile.client > sample/Dockerfile + docker/Dockerfile.agent > sample/Dockerfile $DOCKER build -t ${ORG}/${PROJECT}/aaf_agent:${VERSION} sample $DOCKER tag ${ORG}/${PROJECT}/aaf_agent:${VERSION} ${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/aaf_agent:${VERSION} $DOCKER tag ${ORG}/${PROJECT}/aaf_agent:${VERSION} ${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/aaf_agent:latest @@ -113,6 +112,9 @@ sed -e 's/${AAF_VERSION}/'${VERSION}'/g' \ -e 's/${DUSER}/'${DUSER}'/g' \ Dockerfile.core >../aaf_${VERSION}/Dockerfile cd .. +echo "#######" +pwd +echo "#######" $DOCKER build -t ${ORG}/${PROJECT}/aaf_core:${VERSION} aaf_${VERSION} $DOCKER tag ${ORG}/${PROJECT}/aaf_core:${VERSION} ${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/aaf_core:${VERSION} $DOCKER tag ${ORG}/${PROJECT}/aaf_core:${VERSION} ${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/aaf_core:latest @@ -123,24 +125,34 @@ cd - # Do all the Containers related to AAF Services ####### if [ $AAF_COMPONENTS == "ALL" ]; then - AAF_COMPONENTS=$(ls ../aaf_${VERSION}/bin | grep -v '\.') + AAF_COMPONENTS=$(cat components) fi -echo "$0: AAF_COMPONENTS=$AAF_COMPONENTS" cp ../sample/bin/pod_wait.sh ../aaf_${VERSION}/bin for AAF_COMPONENT in ${AAF_COMPONENTS}; do echo Building aaf_$AAF_COMPONENT... + if [ "hello" = "${AAF_COMPONENT}" ]; then + echo Building Hello separately + DF="Dockerfile.hello" + cp -Rf ../sample/etc ../aaf_${VERSION}/etc + else + DF="Dockerfile.ms" + fi sed -e 's/${AAF_VERSION}/'${VERSION}'/g' \ -e 's/${AAF_COMPONENT}/'${AAF_COMPONENT}'/g' \ -e 's/${DOCKER_REPOSITORY}/'${DOCKER_REPOSITORY}'/g' \ -e 's/${DUSER}/'${DUSER}'/g' \ - Dockerfile.ms >../aaf_${VERSION}/Dockerfile + $DF >../aaf_${VERSION}/Dockerfile cd .. $DOCKER build -t ${ORG}/${PROJECT}/aaf_${AAF_COMPONENT}:${VERSION} aaf_${VERSION} $DOCKER tag ${ORG}/${PROJECT}/aaf_${AAF_COMPONENT}:${VERSION} ${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/aaf_${AAF_COMPONENT}:${VERSION} $DOCKER tag ${ORG}/${PROJECT}/aaf_${AAF_COMPONENT}:${VERSION} ${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/aaf_${AAF_COMPONENT}:latest rm aaf_${VERSION}/Dockerfile + if [ -e aaf_${VERSION}/etc ]; then + rm -Rf aaf_${VERSION}/etc + fi cd - - done + +# Final cleanup rm ../aaf_${VERSION}/bin/pod_wait.sh diff --git a/auth/helm/.gitignore b/auth/helm/.gitignore index 1c5fad92..44cae669 100644 --- a/auth/helm/.gitignore +++ b/auth/helm/.gitignore @@ -1,3 +1,4 @@ aaf.orig/ pause/ aaf.new/ +aaf.props diff --git a/auth/helm/aaf-hello/.helmignore b/auth/helm/aaf-hello/.helmignore new file mode 100644 index 00000000..f0c13194 --- /dev/null +++ b/auth/helm/aaf-hello/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/auth/helm/aaf-hello/Chart.yaml b/auth/helm/aaf-hello/Chart.yaml new file mode 100644 index 00000000..3b23f6d0 --- /dev/null +++ b/auth/helm/aaf-hello/Chart.yaml @@ -0,0 +1,25 @@ +######### +## ============LICENSE_START==================================================== +## org.onap.aaf +## =========================================================================== +## Copyright (c) 2017 AT&T Intellectual Property. 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. +## ============LICENSE_END==================================================== +## + +apiVersion: v1 +appVersion: "1.0" +description: AAF Hello Helm Chart +name: aaf-hello +version: 2.1.11-SNAPSHOT diff --git a/auth/helm/aaf/aaf.sh b/auth/helm/aaf-hello/aaf.sh index 5bb83515..5bb83515 100644 --- a/auth/helm/aaf/aaf.sh +++ b/auth/helm/aaf-hello/aaf.sh diff --git a/auth/helm/aaf-hello/templates/NOTES.txt b/auth/helm/aaf-hello/templates/NOTES.txt new file mode 100644 index 00000000..a6805571 --- /dev/null +++ b/auth/helm/aaf-hello/templates/NOTES.txt @@ -0,0 +1 @@ +AAF Persistence basics loaded diff --git a/auth/helm/aaf/templates/aaf-hello.yaml b/auth/helm/aaf-hello/templates/aaf-hello.yaml index 7c91c856..c114e5eb 100644 --- a/auth/helm/aaf/templates/aaf-hello.yaml +++ b/auth/helm/aaf-hello/templates/aaf-hello.yaml @@ -52,63 +52,51 @@ spec: spec: volumes: # Use this Pod Sharing dir to declare various States of starting - - name: {{ .Chart.Name }}-config-vol - persistentVolumeClaim: - claimName: {{ .Chart.Name }}-config-pvc - - name: {{ .Chart.Name }}-status-vol - persistentVolumeClaim: - claimName: {{ .Chart.Name }}-status-pvc + - name: hello-config-vol + emptyDir: {} initContainers: - - name: {{ .Chart.Name }}-config-container - image: {{ .Values.image.repository }}onap/aaf/aaf_config:{{ .Values.image.version }} + - name: hello-config-container + image: {{ .Values.image.repository }}onap/aaf/aaf_agent:{{ .Values.image.version }} imagePullPolicy: IfNotPresent - command: ["bash","/opt/app/aaf_config/bin/agent.sh"] volumeMounts: - - mountPath: "/opt/app/osaaf" - name: {{ .Chart.Name }}-config-vol + - mountPath: "/opt/app/osaaf/local" + name: hello-config-vol + command: ["bash","-c","cd /opt/app/osaaf/local && /opt/app/aaf_config/bin/agent.sh place aaf@aaf.osaaf.org aaf"] env: - - name: AAF_ENV - value: "{{ .Values.cadi.aaf_env }}" + - name: "AAF_ENV" + value: "DEV" + - name: "AAF_FQDN" + value: "aaf-locate.onap" + - name: "APP_FQDN" + value: "aaf" + - name: "APP_FQI" + value: "aaf@aaf.osaaf.org" - name: LATITUDE value: "{{ .Values.cadi.cadi_latitude }}" - name: LONGITUDE value: "{{ .Values.cadi.cadi_longitude }}" - - name: aaf_locator_container - value: "helm" - - name: aaf_locator_container_ns + - name: "CONTAINER_NS" valueFrom: fieldRef: fieldPath: metadata.namespace - - name: aaf_locate_url - value: "https://aaf-locate.onap:8095" - - name: aaf_locator_public_hostname - value: "aaf.osaaf.org" -# - name: CASSANDRA_USER -# value: "" -# - name: CASSANDRA_PASSWORD -# value: "" -# - name: CASSANDRA_PORT -# value: "" - containers: + - name: "DEPLOY_FQI" + value: "deployer@people.osaaf.org" + - name: "DEPLOY_PASSWORD" + value: "demo123456!" + ### ### AAF-HELLO ### - - name: {{ .Chart.Name }}-hello + containers: + - name: aaf-hello image: {{ .Values.image.repository }}onap/aaf/aaf_hello:{{ .Values.image.version }} imagePullPolicy: IfNotPresent - command: ["/bin/bash","-c","cd /opt/app/aaf && /bin/bash bin/pod_wait.sh aaf-hello aaf-locate && exec bin/hello"] + command: ["/bin/bash","-c","cd /opt/app/aaf && exec bin/hello"] volumeMounts: - - mountPath: "/opt/app/osaaf" - name: {{ .Chart.Name }}-config-vol - - mountPath: "/opt/app/aaf/status" - name: {{ .Chart.Name }}-status-vol + - mountPath: "/opt/app/osaaf/local" + name: hello-config-vol ports: - name: aaf-hello protocol: TCP containerPort: 8130 - env: - - name: aaf_locator_ns - valueFrom: - fieldRef: - fieldPath: metadata.namespace diff --git a/auth/helm/aaf-hello/values.yaml b/auth/helm/aaf-hello/values.yaml new file mode 100644 index 00000000..d5fa7476 --- /dev/null +++ b/auth/helm/aaf-hello/values.yaml @@ -0,0 +1,77 @@ +######### +## ============LICENSE_START==================================================== +## org.onap.aaf +## =========================================================================== +## Copyright (c) 2017 AT&T Intellectual Property. 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. +## ============LICENSE_END==================================================== +## +# +# Default values for aaf. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +global: + persistence: + enabled: true + common: + namespace: "onap" + +replicas: + hello: 1 + +ingress: + enabled: false + +cadi: + hostname: "aaf.onap" + cadi_latitude: "38.0" + cadi_longitude: "-72.0" + aaf_env: "DEV" + +persistence: + # Note: Minikube will persist to /data on your host machine + mountPath: "/data/aaf" + hello: + volumeReclaimPolicy: Retain + accessMode: ReadWriteOnce + size: 1Gi + mountSubPath: "config" + storageClass: "manual" + +image: + # When using locally built Docker Container, set Repository to "" + repository: "" + # When using Docker Repo, add, and include trailing "/" + # repository: nexus3.onap.org:10003/ + # repository: localhost:5000/ + version: 2.1.11-SNAPSHOT + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/auth/helm/aaf/templates/cass_pv.yaml b/auth/helm/aaf/templates/aaf-cass-pv.yaml index 48633a21..48633a21 100644 --- a/auth/helm/aaf/templates/cass_pv.yaml +++ b/auth/helm/aaf/templates/aaf-cass-pv.yaml diff --git a/auth/helm/aaf/templates/cass_pvc.yaml b/auth/helm/aaf/templates/aaf-cass-pvc.yaml index 9fcbe339..9fcbe339 100644 --- a/auth/helm/aaf/templates/cass_pvc.yaml +++ b/auth/helm/aaf/templates/aaf-cass-pvc.yaml diff --git a/auth/helm/aaf/templates/config_pv.yaml b/auth/helm/aaf/templates/aaf-config-pv.yaml index 7066f520..7066f520 100644 --- a/auth/helm/aaf/templates/config_pv.yaml +++ b/auth/helm/aaf/templates/aaf-config-pv.yaml diff --git a/auth/helm/aaf/templates/config_pvc.yaml b/auth/helm/aaf/templates/aaf-config-pvc.yaml index 9136712b..9136712b 100644 --- a/auth/helm/aaf/templates/config_pvc.yaml +++ b/auth/helm/aaf/templates/aaf-config-pvc.yaml diff --git a/auth/helm/aaf/templates/aaf-gui.yaml b/auth/helm/aaf/templates/aaf-gui.yaml index 2a509b76..dcff5e7b 100644 --- a/auth/helm/aaf/templates/aaf-gui.yaml +++ b/auth/helm/aaf/templates/aaf-gui.yaml @@ -31,8 +31,9 @@ spec: ports: - name: aaf-gui protocol: TCP - port: 8200 nodePort: 30083 + port: 8200 + targetPort: 8200 --- apiVersion: apps/v1 kind: Deployment diff --git a/auth/helm/aaf/templates/status_pv.yaml b/auth/helm/aaf/templates/aaf-status-pv.yaml index 418a368b..418a368b 100644 --- a/auth/helm/aaf/templates/status_pv.yaml +++ b/auth/helm/aaf/templates/aaf-status-pv.yaml diff --git a/auth/helm/aaf/templates/status_pvc.yaml b/auth/helm/aaf/templates/aaf-status-pvc.yaml index 2a402ff9..2a402ff9 100644 --- a/auth/helm/aaf/templates/status_pvc.yaml +++ b/auth/helm/aaf/templates/aaf-status-pvc.yaml diff --git a/auth/helm/aaf/values.yaml b/auth/helm/aaf/values.yaml index 7556f251..abea8902 100644 --- a/auth/helm/aaf/values.yaml +++ b/auth/helm/aaf/values.yaml @@ -35,7 +35,6 @@ replicas: oauth: 1 cm: 1 gui: 1 - hello: 0 ingress: enabled: false @@ -49,11 +48,11 @@ cadi: persistence: # Note: Minikube will persist to /data on your host machine mountPath: "/data/aaf" - config: + cass: volumeReclaimPolicy: Retain accessMode: ReadWriteOnce - size: 2Gi - mountSubPath: "config" + size: 10Gi + mountSubPath: "cass" storageClass: "manual" status: volumeReclaimPolicy: Delete @@ -61,11 +60,17 @@ persistence: size: 10M mountSubPath: "status" storageClass: "manual" - cass: + config: volumeReclaimPolicy: Retain accessMode: ReadWriteOnce - size: 10Gi - mountSubPath: "cass" + size: 2Gi + mountSubPath: "config" + storageClass: "manual" + hello: + volumeReclaimPolicy: Retain + accessMode: ReadWriteOnce + size: 1Gi + mountSubPath: "config" storageClass: "manual" image: diff --git a/auth/sample/bin/client.sh b/auth/sample/bin/client.sh index 9b146c5f..42fe4d94 100755 --- a/auth/sample/bin/client.sh +++ b/auth/sample/bin/client.sh @@ -80,6 +80,12 @@ if [ ! -e "$DOT_AAF/keyfile" ]; then if [ ! "${DEPLOY_PASSWORD}" = "" ]; then echo aaf_password=enc:$(sso_encrypt ${DEPLOY_PASSWORD}) >> ${SSO} fi + if [ ! -z "${CONTAINER_NS}" ]; then + echo "aaf_locator_container_ns=${CONTAINER_NS}" >> ${SSO} + fi + if [ ! -z "${AAF_ENV}" ]; then + echo "aaf_env=${AAF_ENV}" >> ${SSO} + fi echo aaf_locate_url=https://${AAF_FQDN}:8095 >> ${SSO} echo aaf_url=https://AAF_LOCATE_URL/AAF_NS.service:${AAF_INTERFACE_VERSION} >> ${SSO} @@ -228,6 +234,10 @@ else taillog) sh /opt/app/osaaf/logs/taillog ;; + testConnectivity|testconnectivity) + echo "--- Test Connectivity ---" + $JAVA -cp $CONFIG/bin/aaf-auth-cmd-*-full.jar org.onap.aaf.cadi.aaf.TestConnectivity $LOCAL/org.osaaf.aaf.props + ;; --help | -?) case "$1" in "") diff --git a/auth/sample/etc/org.osaaf.aaf.cm.props b/auth/sample/etc/org.osaaf.aaf.cm.props index 8d113711..c8e383c2 100644 --- a/auth/sample/etc/org.osaaf.aaf.cm.props +++ b/auth/sample/etc/org.osaaf.aaf.cm.props @@ -26,7 +26,7 @@ cadi_prop_files=/opt/app/osaaf/local/org.osaaf.aaf.props:/opt/app/osaaf/etc/org. aaf_locator_entries=cm port=8150 aaf_locator_public_port.helm=30084 -# aaf_locator_public_port.oom= +aaf_locator_public_port.oom=31114 #Certman cm_public_dir=/opt/app/osaaf/public diff --git a/auth/sample/etc/org.osaaf.aaf.fs.props b/auth/sample/etc/org.osaaf.aaf.fs.props index 02dc0ac9..8233d020 100644 --- a/auth/sample/etc/org.osaaf.aaf.fs.props +++ b/auth/sample/etc/org.osaaf.aaf.fs.props @@ -24,7 +24,7 @@ cadi_prop_files=/opt/app/osaaf/local/org.osaaf.aaf.props:/opt/app/osaaf/etc/org.osaaf.aaf.log4j.props aaf_locator_entries=fs port=8096 -aaf_locator_port.helm=30085 -# aaf_locator_port.oom= +aaf_locator_public_port.helm=30085 +aaf_locator_public_port.oom=31115 aaf_public_dir=/opt/app/osaaf/public diff --git a/auth/sample/etc/org.osaaf.aaf.gui.props b/auth/sample/etc/org.osaaf.aaf.gui.props index caad2080..ce2b6bee 100644 --- a/auth/sample/etc/org.osaaf.aaf.gui.props +++ b/auth/sample/etc/org.osaaf.aaf.gui.props @@ -24,8 +24,8 @@ cadi_prop_files=/opt/app/osaaf/local/org.osaaf.aaf.props:/opt/app/osaaf/etc/org.osaaf.aaf.log4j.props:/opt/app/osaaf/etc/org.osaaf.aaf.orgs.props aaf_locator_entries=gui port=8200 -aaf_locator_port.helm=30083 -#aaf_locator_port.oom= +aaf_locator_public_port.helm=30083 +aaf_locator_public_port.oom=31113 aaf_gui_title=AAF aaf_gui_copyright=(c) 2018 AT&T Intellectual Property. All rights reserved. diff --git a/auth/sample/etc/org.osaaf.aaf.hello.props b/auth/sample/etc/org.osaaf.aaf.hello.props index ea31b3a6..89434685 100644 --- a/auth/sample/etc/org.osaaf.aaf.hello.props +++ b/auth/sample/etc/org.osaaf.aaf.hello.props @@ -25,5 +25,5 @@ cadi_prop_files=/opt/app/osaaf/local/org.osaaf.aaf.props:/opt/app/osaaf/etc/org. aaf_locator_entries=hello port=8130 aaf_locator_public_port.helm=30086 -#aaf_locator_public_port.oom= +aaf_locator_public_port.oom=31116 diff --git a/auth/sample/etc/org.osaaf.aaf.locate.props b/auth/sample/etc/org.osaaf.aaf.locate.props index a132abd6..0290a2ec 100644 --- a/auth/sample/etc/org.osaaf.aaf.locate.props +++ b/auth/sample/etc/org.osaaf.aaf.locate.props @@ -25,6 +25,6 @@ cadi_prop_files=/opt/app/osaaf/local/org.osaaf.aaf.props:/opt/app/osaaf/etc/org. aaf_locator_entries=locate port=8095 aaf_locator_public_port.helm=30081 -#aaf_locator_public_port.oom= +aaf_locator_public_port.oom=31111 diff --git a/auth/sample/etc/org.osaaf.aaf.oauth.props b/auth/sample/etc/org.osaaf.aaf.oauth.props index d9b17064..8c813898 100644 --- a/auth/sample/etc/org.osaaf.aaf.oauth.props +++ b/auth/sample/etc/org.osaaf.aaf.oauth.props @@ -22,8 +22,10 @@ ## cadi_prop_files=/opt/app/osaaf/local/org.osaaf.aaf.props:/opt/app/osaaf/etc/org.osaaf.aaf.log4j.props:/opt/app/osaaf/local/org.osaaf.aaf.cassandra.props aaf_locator_entries=oauth,token,introspect +aaf_locator_fqdn.helm=%CNS.aaf-oauth +aaf_locator_fqdn.oom=%CNS.aaf-oauth port=8140 aaf_locator_public_port.helm=30082 -#aaf_locator_public_port.oom= +aaf_locator_public_port.oom=31112 diff --git a/auth/sample/etc/org.osaaf.aaf.service.props b/auth/sample/etc/org.osaaf.aaf.service.props index 87924ba8..3af5f808 100644 --- a/auth/sample/etc/org.osaaf.aaf.service.props +++ b/auth/sample/etc/org.osaaf.aaf.service.props @@ -25,5 +25,5 @@ cadi_prop_files=/opt/app/osaaf/local/org.osaaf.aaf.props:/opt/app/osaaf/etc/org. aaf_locator_entries=service port=8100 aaf_locator_public_port.helm=30080 -#aaf_locator_public_port.oom= +aaf_locator_public_port.oom=31110 diff --git a/auth/sample/local/initialConfig.props b/auth/sample/local/initialConfig.props index 93cfae56..2bcaf7c4 100644 --- a/auth/sample/local/initialConfig.props +++ b/auth/sample/local/initialConfig.props @@ -28,7 +28,7 @@ cadi_protocols=TLSv1.1,TLSv1.2 # 3) Helm (Kubernetes and Helm)
# 4) OOM (Kubernetes with OOM)
################################
-aaf_locator_ns=AAF_NS
+aaf_locator_app_ns=%AAF_NS
aaf_locator_name=%NS.%N
aaf_locator_name.docker=%CNS.%NS.%N
aaf_locator_name.helm=%CNS.%NS.%N
|