Skip to content

Commit

Permalink
refactoring code to include model across local, logical, and vault na…
Browse files Browse the repository at this point in the history
…mespaces
  • Loading branch information
ndenny committed Jun 3, 2024
1 parent b268c51 commit 68b1bb4
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 134 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,27 @@
# bastion
Scripts that integrate RCAC Fortress, MS Teams, etc.

## Concepts
Data assets can have concurrent presence in three different namespaces:
1. the host file system
2. the logical zone (kind of like a "location" when configuring a web server)
3. the vault

Backup operations are semantically done in the logical and vault file systems.
This allows for (example) data assets to be moved from one host to another, or possibly replicated onto several hosts.

### Host File System
Data assets in the host file system are described as fully qualified (i.e. from the root of the file system) POSIX paths.


### Logical File System (Resource Zone)
Data assets also belong to a logical "resource" zone.
In an zone, assets are fully described via an Asset Resource Key (ARK) which is a triple of (site.name, zone.name, asset.name)


### Vault Namespace
Data assets can be fully copied or differentially updated in a vault.
These objects are described using a BLONDE (BLOb Name and Description Encoded), which is a compact, unique name for a backup object that encodes a reference to the source ARK, its lineage, and time of commit.
In the vault, a BLONDE can either be an anchor (full backup) or a differential.
References to assets are done by "badge" - which is a 40 bit chunk from a SHAKE128 hash of the asset's ARK (in CURIE format).
Time is also highly compressed using "Quantim"
15 changes: 12 additions & 3 deletions bin/bastion.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,11 @@ def do_help(self, comargs, comdex):
#-- backup operations |
#----------------------
def do_backup_asset(self, comargs, comdex):
"""
backup asset {ARK}
"""
ark = ARK(comdex[2])
print(ark)
# print(ark)
site = self.site(ark.site)
vault = self.vault('fortress')
vault.provision(ark)
Expand Down Expand Up @@ -205,8 +208,14 @@ def do_export_asset_manifest(self, comargs, comdex):

if __name__ == '__main__':
app = App().configured()
app.run( )

#app.run( )
comdex = dict(enumerate(sys.argv[1:]))
fortress = app.vault('fortress')
rusina = app.site('rusina')
ark = ARK(comdex[2])
ds = rusina.asset(ark)
fortress.provision(ark)


#bastion site {site} backup
#bastion zone {zone} backup
Expand Down
2 changes: 2 additions & 0 deletions lib/Bastion/Actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
I am the functions that execute the various actions of the bastion app.
"""
def doBackupAsset(site, vault, ark):
raise NotImplementedError
10 changes: 9 additions & 1 deletion lib/Bastion/Common.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def RDN(x):
"""
Answers the relatively distinguishing name (RDN) of object, x.
If x is a string, it is assumed that x is the name.
If x is an object with a CN attribute, answers x.RDN
If x is an object with an RDN attribute, answers x.RDN
"""
if isinstance(x, str):
return x
Expand Down Expand Up @@ -135,6 +135,9 @@ def __new__(cls, *args, **kwargs):

@property
def path(self):
"""
A Pure POSIX path object for the path relative to my zone.
"""
return pathlib.PurePosixPath(self[1])

def __str__(self):
Expand All @@ -148,6 +151,11 @@ def __repr__(self):

@property
def ref(self):
"""
The qualified name of this object within my space.
If my name isn't qualified (i.e. has no extra "arguments"), then this is the string representation of my path;
however, if my name is qualified, then I include the arguments after the typical URL "?" separator.
"""
if self.args:
return "{}?{}".format(str(self.path), self.args)
else:
Expand Down
44 changes: 22 additions & 22 deletions lib/Bastion/Curator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
from .Model import ARK
from .Chronology import Quantim

class BLOND(canTextify):

class BLONDE(canTextify):
"""
BLOb Name and Description
I am a structured name describing a single ark.
An ark is typically a BLOB or file, hence I am usually BLOB name (e.g. in an S3 bucket) or a file name.
BLOb Name and Description Encoding
I am a structured name describing a point in time for a single ARK.
"""
def __init__(self, asset, detail, basis = None, when = None):
if isinstance(asset, ARK):
Expand Down Expand Up @@ -57,25 +57,25 @@ def __str__(self):
return self.encode(self.badge, self.when, self.detail, self.basis)

@classmethod
def decode(cls, blond):
def decode(cls, blonde):
ds = Thing(**{
'badge': blond[0:8],
'when': Quantim(blond[8:16]),
'detail': blond[16],
'basis': blond[17:20],
'badge': blonde[0:8],
'when': Quantim(blonde[8:16]),
'detail': blonde[16],
'basis': blonde[17:20],
})
return cls(ds.badge, ds.when, ds.detail, ds.basis)

@classmethod
def fullBackup(cls, ark, **kwargs):
def forFullBackup(cls, ark, **kwargs):
when = kwargs.get('when', datetime.datetime.utcnow())
basis = kwargs.get('basis', Boggle(3))
return cls(ark, 'F', basis, when)

@classmethod
def diffBackup(cls, anchor, **kwargs):
def forDiffBackup(cls, anchor, **kwargs):
"""
Given a full backup reference (BLOND), I generate a new BLOND for a differential backup.
Given a full backup reference (BLONDE), I generate a new BLONDE for a differential backup.
"""
when = kwargs.get('when', datetime.datetime.utcnow())
return cls(anchor.badge, 'D', anchor.basis, when)
Expand Down Expand Up @@ -136,12 +136,12 @@ class Manifest:
def __init__(self, asset, items):
self.asset = ARK(asset)
self.badge = self.asset.badge
self._items = None #-- a sorted tuple of BLONDs
self._items = None #-- a sorted tuple of BLONDEs
self._snaps = None
self._anchors = None #-- a map of anchor layer -> blob name

blonds = [BLOND(item) for item in items]
self._items = tuple(sorted([blond for blond in blonds if (blond.badge == self.badge)], key = lambda b: b.RDN))
blondes = [BLONDE(item) for item in items]
self._items = tuple(sorted([blonde for blonde in blondes if (blonde.badge == self.badge)], key = lambda b: b.RDN))
self._anchors = dict([(item.layer, item) for item in self._items if item.isAnchor])

def __iter__(self):
Expand Down Expand Up @@ -179,37 +179,37 @@ def thread(self, ankle):
anchor = ankle
elif isinstance(ankle, snap):
anchor = ankle.anchor.basis
elif isinstance(ankle, BLOND):
elif isinstance(ankle, BLONDE):
anchor = ankle.basis
return Thread([snap for snap in self.snaps if snap.basis == anchor])

def anchor(self, item):
"""
I answer the blond to the anchor for the given item.
.anchor(item:BLOND)
I answer the BLONDE to the anchor for the given item.
.anchor(item:BLONDE)
.anchor(item:str)
.anchor(item:Snap)
"""
if isinstance(item, BLOND):
if isinstance(item, BLONDE):
return self._anchors[item.anchor]
if isinstance(item, str):
return self.anchor( BLOND.decode(item) )
return self.anchor( BLONDE.decode(item) )
if isinstance(item, Snap):
return item.anchor
raise ValueError

@property
def anchors(self):
"""
I answer a chronologically (earliest -> latest) tuple of BLONDs for my anchor items.
I answer a chronologically (earliest -> latest) tuple of BLONDEs for my anchor items.
"""
return tuple(sorted(self._anchors.values, key = lambda item: item.RDN))



class Snap:
"""
A snap is a pair of blonds (differential, anchor)
A snap is a pair of BLONDEs (differential, anchor)
In the case that the snap is an anchor then (anchor, anchor)
"""
def __init__(self, head, anchor):
Expand Down
41 changes: 27 additions & 14 deletions lib/Bastion/HPSS.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from Bastion.Common import Thing, Unknown
import Bastion.Model
from Bastion.Curator import BLOND
from Bastion.Curator import BLONDE



Expand Down Expand Up @@ -382,34 +382,47 @@ def manifest(self, *args):
manifest(site, zone, asset)
"""
if len(args) == 1:
ark = args[0]
return self._manifest_ark( args[0] )
elif len(args) == 3:
site, zone, asset = args
ark = ARK(site, zone, asset)
return self._manifest_site_zone_asset( args[0], args[1], args[2] )
else:
raise ValueError


def _manifest_ark(self, ark):
#-- The contents of {root}/{site}/{zone}/{asset} are backup blobs
#-- Each name in this folder is a "BLOND" (BLOb Name and Descriptor)
#-- The manifest a catalog of all of the backup objects for the asset.
fls = self.hsi.ls(self.root / ark.site / ark.zone / ark.asset)
blonds = [fl.path.name for fl in fls]
manifest = Bastion.Curator.Manifest(ark, blonds)
blondes = [fl.path.name for fl in fls]
manifest = Bastion.Curator.Manifest(ark, blondes)

return manifest

def _manifest_site_zone_asset(self, site, zone, asset):
return self._manifest_ark( ARK(site, zone, asset) )


def provision(self, *args):
"""
provision(ark) - ensures that the site, zone, and asset folders exist.
provision(site, zone, asset_name) - an alias for provision(ark)
provision(asset) - given an instance of Asset, provision the necessary site, zone, and asset folders.
"""
if len(args) == 1:
ark = args[0]
self.hsi.mkdirs(self.root / ark.site / ark.zone / ark.asset)
return self._provision_ark( args[0] )
elif len(args) == 3:
site, zone, asset = args
ark = ARK(site, zone, asset)
self.provision(ark)
return self._provision_site_zone_asset( args[0], args[1], args[2] )
else:
raise ValueError

def htar(self, asset, **kwargs):
def _provision_ark(self, ark):
self.hsi.mkdirs(self.root / ark.site / ark.zone / ark.asset)

def _provision_site_zone_asset(self, site, zone, asset_name):
return self._provision_ark( ARK(site, zone, asset_name) )

def push(self, asset, **kwargs):
detail = kwargs.get('detail', 'F')
localf = asset.path
ark = asset.ARK
Expand All @@ -419,11 +432,11 @@ def htar(self, asset, **kwargs):
'site': ark.site,
'zone': ark.zone,
'asset': ark.asset,
'blond': BLOND(ark, detail),
'blonde': BLONDE(ark, detail),
'localf': localf
}

comargs = [opts['htar'], "-Hverify=1", "-cfvf", "{site}/{zone}/{asset}/{blond}".format(**opts), "{localf}".format(**opts)]
comargs = [str(opts['htar']), "-Hverify=1", "-c", "-v", "-f", "{site}/{zone}/{asset}/{blonde}.tar".format(**opts), "{localf}".format(**opts)]
proc = subprocess.run(comargs, capture_output = True, check = True)
stdout = proc.stdout.decode('utf-8')
stderr = proc.stderr.decode('utf-8')
Expand Down
Loading

0 comments on commit 68b1bb4

Please sign in to comment.